From 399c140ed9b41e9de7957a44afca8c458c0e2193 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Fri, 18 Oct 2024 22:46:23 +0100 Subject: [PATCH 01/31] Remove FileDialog references from DiscImage.h. DiscImage is part of beeb_lib, but FileDialog is part of b2. --- src/b2/BeebWindow.cpp | 5 ++++- src/b2/DirectDiscImage.cpp | 7 ++++--- src/b2/DirectDiscImage.h | 2 +- src/b2/MemoryDiscImage.cpp | 6 ++++-- src/b2/MemoryDiscImage.h | 2 +- src/beeb/include/beeb/DiscImage.h | 11 ++++++++--- src/beeb/tests/test_common.cpp | 6 +++--- 7 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/b2/BeebWindow.cpp b/src/b2/BeebWindow.cpp index 1b4f2da7..fc1c8ac2 100644 --- a/src/b2/BeebWindow.cpp +++ b/src/b2/BeebWindow.cpp @@ -1873,7 +1873,10 @@ void BeebWindow::DoDiscDriveSubMenu(int drive, if (ImGui::MenuItem("Save copy as...")) { SaveFileDialog fd(RECENT_PATHS_DISC_IMAGE); - disc_image->AddFileDialogFilter(&fd); + std::vector filters = disc_image->GetFileDialogFilters(); + for (const FileDialogFilter &filter : filters) { + fd.AddFilter(filter.name, filter.extensions); + } fd.AddAllFilesFilter(); std::string path; diff --git a/src/b2/DirectDiscImage.cpp b/src/b2/DirectDiscImage.cpp index e1bf56a6..2594bccd 100644 --- a/src/b2/DirectDiscImage.cpp +++ b/src/b2/DirectDiscImage.cpp @@ -100,11 +100,12 @@ std::string DirectDiscImage::GetDescription() const { ////////////////////////////////////////////////////////////////////////// // TODO - basically the same as MemoryDiscImage. -void DirectDiscImage::AddFileDialogFilter(FileDialog *fd) const { +std::vector DirectDiscImage::GetFileDialogFilters() const { if (const char *ext = GetExtensionFromDiscGeometry(m_geometry)) { - std::string pattern = std::string("*") + ext; - fd->AddFilter("BBC disc image", {pattern}); + return {{"BBC disc image", {ext}}}; } + + return {}; } ////////////////////////////////////////////////////////////////////////// diff --git a/src/b2/DirectDiscImage.h b/src/b2/DirectDiscImage.h index f3e59f9a..417e2b37 100644 --- a/src/b2/DirectDiscImage.h +++ b/src/b2/DirectDiscImage.h @@ -44,7 +44,7 @@ class DirectDiscImage : public DiscImage { std::string GetLoadMethod() const override; std::string GetDescription() const override; - void AddFileDialogFilter(FileDialog *fd) const override; + std::vector GetFileDialogFilters() const override; bool SaveToFile(const std::string &file_name, const LogSet &logs) const override; diff --git a/src/b2/MemoryDiscImage.cpp b/src/b2/MemoryDiscImage.cpp index 94e93a65..7951beb7 100644 --- a/src/b2/MemoryDiscImage.cpp +++ b/src/b2/MemoryDiscImage.cpp @@ -352,10 +352,12 @@ std::string MemoryDiscImage::GetDescription() const { ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -void MemoryDiscImage::AddFileDialogFilter(FileDialog *fd) const { +std::vector MemoryDiscImage::GetFileDialogFilters() const { if (const char *ext = GetExtensionFromDiscGeometry(m_data->geometry)) { - fd->AddFilter("BBC disc image", {ext}); + return {{"BBC disc image", {ext}}}; } + + return {}; } ////////////////////////////////////////////////////////////////////////// diff --git a/src/b2/MemoryDiscImage.h b/src/b2/MemoryDiscImage.h index 73ef985d..1c6e676a 100644 --- a/src/b2/MemoryDiscImage.h +++ b/src/b2/MemoryDiscImage.h @@ -45,7 +45,7 @@ class MemoryDiscImage : public DiscImage { std::string GetName() const override; std::string GetLoadMethod() const override; std::string GetDescription() const override; - void AddFileDialogFilter(FileDialog *fd) const override; + std::vector GetFileDialogFilters() const override; bool SaveToFile(const std::string &file_name, const LogSet &logs) const override; //void SetNameAndLoadMethod(std::string name,std::string load_method); diff --git a/src/beeb/include/beeb/DiscImage.h b/src/beeb/include/beeb/DiscImage.h index c5977b10..031b1ebe 100644 --- a/src/beeb/include/beeb/DiscImage.h +++ b/src/beeb/include/beeb/DiscImage.h @@ -6,8 +6,8 @@ #include #include +#include -class FileDialog; struct LogSet; ////////////////////////////////////////////////////////////////////////// @@ -42,6 +42,11 @@ struct DiscImageSummary { std::string hash; }; +struct FileDialogFilter { + std::string name; + std::vector extensions; +}; + class DiscImage : public std::enable_shared_from_this { public: DiscImage(); @@ -74,10 +79,10 @@ class DiscImage : public std::enable_shared_from_this { virtual std::string GetDescription() const = 0; - // Adds file dialog filter suitable for selecting files of + // Get file dialog filters suitable for selecting files of // whatever type of disc image this image was loaded from. Use // this to populate a save dialog when saving a copy. - virtual void AddFileDialogFilter(FileDialog *fd) const = 0; + virtual std::vector GetFileDialogFilters() const = 0; // Save a copy of this disc image to the given file. If // successful, returns a clone with the new name and whatever load diff --git a/src/beeb/tests/test_common.cpp b/src/beeb/tests/test_common.cpp index 142b217a..a578ff8e 100644 --- a/src/beeb/tests/test_common.cpp +++ b/src/beeb/tests/test_common.cpp @@ -183,7 +183,7 @@ class TestDiscImage : public DiscImage { std::string GetName() const override; std::string GetLoadMethod() const override; std::string GetDescription() const override; - void AddFileDialogFilter(FileDialog *fd) const override; + std::vector GetFileDialogFilters() const override; bool SaveToFile(const std::string &file_name, const LogSet &logs) const override; bool Read(uint8_t *value, uint8_t side, @@ -264,8 +264,8 @@ std::string TestDiscImage::GetDescription() const { ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -void TestDiscImage::AddFileDialogFilter(FileDialog *fd) const { - (void)fd; +std::vector TestDiscImage::GetFileDialogFilters() const { + return {}; } ////////////////////////////////////////////////////////////////////////// From 3c3797b087fe872a4440aba584f335990010b222 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sat, 26 Oct 2024 14:57:53 +0100 Subject: [PATCH 02/31] Auto-detect whether strlcpy needs defining. --- src/shared/CMakeLists.txt | 14 ++++++++++++++ src/shared/c/system.cpp | 32 ++++++++++++++++++++++++++++++++ src/shared/c/system_windows.cpp | 28 ---------------------------- src/shared/h/shared/system.h | 2 +- 4 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/shared/CMakeLists.txt b/src/shared/CMakeLists.txt index 17fdd22d..860cbcfc 100644 --- a/src/shared/CMakeLists.txt +++ b/src/shared/CMakeLists.txt @@ -8,6 +8,13 @@ endif() ########################################################################## ########################################################################## +check_cxx_source_compiles("#include +typedef decltype(&strlcpy) T;int main(void){return 0;}" HAVE_STRLCPY) +message(STATUS "strlcpy exists: ``${HAVE_STRLCPY}''") + +########################################################################## +########################################################################## + set(H h/shared) set(SRCS @@ -50,6 +57,13 @@ endif() add_library(shared_lib STATIC ${SRCS}) +if(HAVE_STRLCPY) + # There are HAVE_STRLCPY defines in SDL and libcurl, so give this + # one a different name in order not to interfere. + # + # It must be public, as this influences system.h. + target_compile_definitions(shared_lib PUBLIC SYSTEM_HAVE_STRLCPY=1) +endif() target_include_directories(shared_lib INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/h) target_include_directories(shared_lib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/h) diff --git a/src/shared/c/system.cpp b/src/shared/c/system.cpp index 8876af6d..ea25c7e3 100644 --- a/src/shared/c/system.cpp +++ b/src/shared/c/system.cpp @@ -60,6 +60,38 @@ void SetCurrentThreadNamev(const char *fmt, va_list v) { ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// +#if !SYSTEM_HAVE_STRLCPY + +// http://www.opensource.apple.com/source/mail_cmds/mail_cmds-24/mail/strlcpy.c +size_t strlcpy(char *dest, const char *src, size_t size) { + char *d = dest; + const char *s = src; + size_t n = size; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (size != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return (s - src - 1); /* count does not include NUL */ +} + +#endif + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + // This goes here because... it has to go somewhere. #define BIT(X, N) (((X)&1 << N) ? '1' : '0') diff --git a/src/shared/c/system_windows.cpp b/src/shared/c/system_windows.cpp index afaba18b..c01977a2 100644 --- a/src/shared/c/system_windows.cpp +++ b/src/shared/c/system_windows.cpp @@ -88,34 +88,6 @@ int vasprintf(char **buf, const char *fmt, va_list v_) { ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -// http://www.opensource.apple.com/source/mail_cmds/mail_cmds-24/mail/strlcpy.c -size_t strlcpy(char *dest, const char *src, size_t size) { - char *d = dest; - const char *s = src; - size_t n = size; - - /* Copy as many bytes as will fit */ - if (n != 0 && --n != 0) { - do { - if ((*d++ = *s++) == 0) - break; - } while (--n != 0); - } - - /* Not enough room in dst, add NUL and traverse rest of src */ - if (n == 0) { - if (size != 0) - *d = '\0'; /* NUL-terminate dst */ - while (*s++) - ; - } - - return (s - src - 1); /* count does not include NUL */ -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - int backtrace(void **array, int size) { (void)array, (void)size; diff --git a/src/shared/h/shared/system.h b/src/shared/h/shared/system.h index 9aafbd92..5c8704e2 100644 --- a/src/shared/h/shared/system.h +++ b/src/shared/h/shared/system.h @@ -282,7 +282,7 @@ int vasprintf(char **buf, const char *fmt, va_list v); ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -#if SYSTEM_LINUX || SYSTEM_WINDOWS +#if !SYSTEM_HAVE_STRLCPY // BSD extension. // From 421acc92c1faa0c1142d1242418588128f46e4c4 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sat, 26 Oct 2024 15:55:32 +0100 Subject: [PATCH 03/31] Fix Linux build. --- src/shared/c/system.cpp | 2 +- src/shared/c/system_linux.cpp | 28 ---------------------------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/src/shared/c/system.cpp b/src/shared/c/system.cpp index ea25c7e3..3cb55326 100644 --- a/src/shared/c/system.cpp +++ b/src/shared/c/system.cpp @@ -84,7 +84,7 @@ size_t strlcpy(char *dest, const char *src, size_t size) { ; } - return (s - src - 1); /* count does not include NUL */ + return (size_t)(s - src - 1); /* count does not include NUL */ } #endif diff --git a/src/shared/c/system_linux.cpp b/src/shared/c/system_linux.cpp index d574a5a3..ca82d0f4 100644 --- a/src/shared/c/system_linux.cpp +++ b/src/shared/c/system_linux.cpp @@ -36,34 +36,6 @@ int IsDebuggerAttached() { ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -// http://www.opensource.apple.com/source/mail_cmds/mail_cmds-24/mail/strlcpy.c -size_t strlcpy(char *dest, const char *src, size_t size) { - char *d = dest; - const char *s = src; - size_t n = size; - - /* Copy as many bytes as will fit */ - if (n != 0 && --n != 0) { - do { - if ((*d++ = *s++) == 0) - break; - } while (--n != 0); - } - - /* Not enough room in dst, add NUL and traverse rest of src */ - if (n == 0) { - if (size != 0) - *d = '\0'; /* NUL-terminate dst */ - while (*s++) - ; - } - - return (size_t)(s - src - 1); /* count does not include NUL */ -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - #if USE_ADDR2LINE struct Module { std::string name; From ce7572cdcaa6f3ba80186be865d1f1c71db70d85 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sat, 26 Oct 2024 16:39:41 +0100 Subject: [PATCH 04/31] Change more Messages to LogSet. (Following on from 34786bd73b97ed1090cc8d7d18c8e7163c8513c6) See #379. --- src/b2/BeebWindow.cpp | 8 ++-- src/b2/DirectDiscImage.cpp | 6 +-- src/b2/DirectDiscImage.h | 2 +- src/b2/DiscGeometry.cpp | 82 +++++++++++++++++------------------ src/b2/DiscGeometry.h | 6 +-- src/b2/HTTPMethodsHandler.cpp | 4 +- src/b2/MemoryDiscImage.cpp | 44 +++++++++---------- src/b2/MemoryDiscImage.h | 7 ++- src/b2/b2.cpp | 4 +- 9 files changed, 81 insertions(+), 82 deletions(-) diff --git a/src/b2/BeebWindow.cpp b/src/b2/BeebWindow.cpp index fc1c8ac2..3b53f521 100644 --- a/src/b2/BeebWindow.cpp +++ b/src/b2/BeebWindow.cpp @@ -1911,7 +1911,7 @@ void BeebWindow::DoDiscImageSubMenu(int drive, bool boot) { } } - std::shared_ptr new_disc_image = DirectDiscImage::CreateForFile(direct_item.path, &m_msg); + std::shared_ptr new_disc_image = DirectDiscImage::CreateForFile(direct_item.path, m_msg); this->DoDiscImageSubMenuItem(drive, std::move(new_disc_image), @@ -1932,10 +1932,10 @@ void BeebWindow::DoDiscImageSubMenu(int drive, bool boot) { file_item.new_disc_data.data(), file_item.new_disc_data.size(), *file_item.new_disc_type->geometry, - &m_msg); + m_msg); } else { new_disc_image = MemoryDiscImage::LoadFromFile(file_item.path, - &m_msg); + m_msg); } this->DoDiscImageSubMenuItem(drive, std::move(new_disc_image), @@ -3270,7 +3270,7 @@ const std::string &BeebWindow::GetConfigName() const { ////////////////////////////////////////////////////////////////////////// void BeebWindow::Launch(const BeebWindowLaunchArguments &arguments) { - std::shared_ptr disc_image = MemoryDiscImage::LoadFromFile(arguments.file_path, &m_msg); + std::shared_ptr disc_image = MemoryDiscImage::LoadFromFile(arguments.file_path, m_msg); if (!disc_image) { return; } diff --git a/src/b2/DirectDiscImage.cpp b/src/b2/DirectDiscImage.cpp index 2594bccd..234b0dcb 100644 --- a/src/b2/DirectDiscImage.cpp +++ b/src/b2/DirectDiscImage.cpp @@ -26,16 +26,16 @@ DirectDiscImage::~DirectDiscImage() { ////////////////////////////////////////////////////////////////////////// std::shared_ptr DirectDiscImage::CreateForFile(std::string path, - Messages *msg) { + const LogSet &logs) { size_t size; bool can_write; if (!GetFileDetails(&size, &can_write, path.c_str())) { - msg->e.f("Couldn't get details for file: %s\n", path.c_str()); + logs.e.f("Couldn't get details for file: %s\n", path.c_str()); return nullptr; } DiscGeometry geometry; - if (!FindDiscGeometryFromFileDetails(&geometry, path.c_str(), size, msg)) { + if (!FindDiscGeometryFromFileDetails(&geometry, path.c_str(), size, &logs)) { return nullptr; } diff --git a/src/b2/DirectDiscImage.h b/src/b2/DirectDiscImage.h index 417e2b37..1948a76c 100644 --- a/src/b2/DirectDiscImage.h +++ b/src/b2/DirectDiscImage.h @@ -27,7 +27,7 @@ class DirectDiscImage : public DiscImage { ~DirectDiscImage(); - static std::shared_ptr CreateForFile(std::string path, Messages *msg); + static std::shared_ptr CreateForFile(std::string path, const LogSet &logs); DirectDiscImage(const DirectDiscImage &) = delete; DirectDiscImage &operator=(const DirectDiscImage &) = delete; diff --git a/src/b2/DiscGeometry.cpp b/src/b2/DiscGeometry.cpp index 0db3e4e3..954fdc78 100644 --- a/src/b2/DiscGeometry.cpp +++ b/src/b2/DiscGeometry.cpp @@ -53,7 +53,7 @@ static const size_t NUM_DDD_GEOMETRIES = sizeof DDD_GEOMETRIES / sizeof DDD_GEOM struct DiscImageType; -typedef bool (*FindDiscGeometryFn)(DiscGeometry *geometry, const char *name, size_t size, Messages *msg, const DiscImageType *disc_image_type); +typedef bool (*FindDiscGeometryFn)(DiscGeometry *geometry, const char *name, size_t size, const LogSet *logs, const DiscImageType *disc_image_type); struct DiscImageType { const char *ext; @@ -76,21 +76,21 @@ struct DiscImageType { ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -static void PrintInvalidSizeMessage(const char *name, Messages *msg) { - msg->e.f("invalid size"); +static void PrintInvalidSizeMessage(const char *name, const LogSet &logs) { + logs.e.f("invalid size"); if (name) { - msg->e.f(" for file: %s", name); + logs.e.f(" for file: %s", name); } - msg->e.f("\n"); + logs.e.f("\n"); } -static bool IsMultipleOfSectorSize(const char *name, size_t size, Messages *msg) { +static bool IsMultipleOfSectorSize(const char *name, size_t size, const LogSet *logs) { if (size % 256 != 0) { - if (msg) { - PrintInvalidSizeMessage(name, msg); + if (logs) { + PrintInvalidSizeMessage(name, *logs); - msg->i.f("(length %zu not a multiple of sector size 256)\n", - size); + logs->i.f("(length %zu not a multiple of sector size 256)\n", + size); } return false; } @@ -98,25 +98,25 @@ static bool IsMultipleOfSectorSize(const char *name, size_t size, Messages *msg) return true; } -static bool FindSingleDensityDiscGeometry(DiscGeometry *geometry, const char *name, size_t size, Messages *msg, const DiscImageType *disc_image_type) { +static bool FindSingleDensityDiscGeometry(DiscGeometry *geometry, const char *name, size_t size, const LogSet *logs, const DiscImageType *disc_image_type) { ASSERT(disc_image_type->num_possible_geometries == 1); ASSERT(!disc_image_type->possible_geometries[0].double_density); *geometry = DiscGeometry(80, 10, 256, disc_image_type->possible_geometries[0].double_sided); - if (!IsMultipleOfSectorSize(name, size, msg)) { + if (!IsMultipleOfSectorSize(name, size, logs)) { return false; } if (size > geometry->GetTotalNumBytes()) { - if (msg) { - PrintInvalidSizeMessage(name, msg); - - msg->i.f("(size %zu bytes is larger than maximum %zu bytes for %d*%zu*%zu sectors)\n", - size, - geometry->GetTotalNumBytes(), - geometry->double_sided ? 2 : 1, - geometry->num_tracks, - geometry->sectors_per_track); + if (logs) { + PrintInvalidSizeMessage(name, *logs); + + logs->i.f("(size %zu bytes is larger than maximum %zu bytes for %d*%zu*%zu sectors)\n", + size, + geometry->GetTotalNumBytes(), + geometry->double_sided ? 2 : 1, + geometry->num_tracks, + geometry->sectors_per_track); } return false; } @@ -124,7 +124,7 @@ static bool FindSingleDensityDiscGeometry(DiscGeometry *geometry, const char *na return true; } -static bool FindDiscGeometryFromFileSize(DiscGeometry *geometry, const char *name, size_t size, Messages *msg, const DiscImageType *disc_image_type) { +static bool FindDiscGeometryFromFileSize(DiscGeometry *geometry, const char *name, size_t size, const LogSet *logs, const DiscImageType *disc_image_type) { for (size_t i = 0; i < disc_image_type->num_possible_geometries; ++i) { const DiscGeometry *g = &disc_image_type->possible_geometries[i]; @@ -134,34 +134,36 @@ static bool FindDiscGeometryFromFileSize(DiscGeometry *geometry, const char *nam } } - if (msg) { - PrintInvalidSizeMessage(name, msg); + if (logs) { + PrintInvalidSizeMessage(name, *logs); - msg->i.f("(size is %zu; valid sizes are: ", size); + logs->i.f("(size is %zu; valid sizes are: ", size); for (size_t i = 0; i < disc_image_type->num_possible_geometries; ++i) { if (i > 0) { - msg->i.f("; "); + logs->i.f("; "); } - msg->i.f("%zu", disc_image_type->possible_geometries[i].GetTotalNumBytes()); + logs->i.f("%zu", disc_image_type->possible_geometries[i].GetTotalNumBytes()); } - msg->i.f(")\n"); + logs->i.f(")\n"); } return false; } -static bool FindADFSDiscGeometry(DiscGeometry *geometry, const char *name, size_t size, Messages *msg, const DiscImageType *disc_image_type) { +static bool FindADFSDiscGeometry(DiscGeometry *geometry, const char *name, size_t size, const LogSet *logs, const DiscImageType *disc_image_type) { (void)disc_image_type; - if (!IsMultipleOfSectorSize(name, size, msg)) { + if (!IsMultipleOfSectorSize(name, size, logs)) { return false; } if (size > ADL_SIZE) { - PrintInvalidSizeMessage(name, msg); + if (logs) { + PrintInvalidSizeMessage(name, *logs); - msg->i.f("(size is %zu; max valid size is %zu", size, ADL_SIZE); + logs->i.f("(size is %zu; max valid size is %zu", size, ADL_SIZE); + } return false; } else if (size > ADM_SIZE && size <= ADL_SIZE) { @@ -312,17 +314,17 @@ bool operator!=(const DiscGeometry &a, const DiscGeometry &b) { bool FindDiscGeometryFromFileDetails(DiscGeometry *geometry, const char *file_name, size_t file_size, - Messages *msg) { + const LogSet *logs) { std::string ext = PathGetExtension(file_name); for (const DiscImageType *type = DISC_IMAGE_TYPES; type->ext; ++type) { if (PathCompare(ext, type->ext) == 0) { - return (*type->find_geometry_fn)(geometry, file_name, file_size, msg, type); + return (*type->find_geometry_fn)(geometry, file_name, file_size, logs, type); } } - if (msg) { - msg->e.f("unknown extension: %s\n", ext.c_str()); + if (logs) { + logs->e.f("unknown extension: %s\n", ext.c_str()); } return false; @@ -334,16 +336,14 @@ bool FindDiscGeometryFromFileDetails(DiscGeometry *geometry, bool FindDiscGeometryFromMIMEType(DiscGeometry *geometry, const char *mime_type, size_t file_size, - Messages *msg) { + const LogSet &logs) { for (const DiscImageType *type = DISC_IMAGE_TYPES; type->ext; ++type) { if (strcmp(mime_type, type->mime_type) == 0) { - return (*type->find_geometry_fn)(geometry, nullptr, file_size, msg, type); + return (*type->find_geometry_fn)(geometry, nullptr, file_size, &logs, type); } } - if (msg) { - msg->e.f("unknown MIME type: %s\n", mime_type); - } + logs.e.f("unknown MIME type: %s\n", mime_type); return false; } diff --git a/src/b2/DiscGeometry.h b/src/b2/DiscGeometry.h index f4a6a574..84b5882f 100644 --- a/src/b2/DiscGeometry.h +++ b/src/b2/DiscGeometry.h @@ -7,7 +7,7 @@ #include #include -class Messages; +struct LogSet; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -58,12 +58,12 @@ extern const std::vector DISC_IMAGE_EXTENSIONS; bool FindDiscGeometryFromFileDetails(DiscGeometry *geometry, const char *file_name, size_t file_size, - Messages *msg); + const LogSet *logs); bool FindDiscGeometryFromMIMEType(DiscGeometry *geometry, const char *mime_type, size_t file_size, - Messages *msg); + const LogSet &logs); ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// diff --git a/src/b2/HTTPMethodsHandler.cpp b/src/b2/HTTPMethodsHandler.cpp index 1b087140..2e59da6b 100644 --- a/src/b2/HTTPMethodsHandler.cpp +++ b/src/b2/HTTPMethodsHandler.cpp @@ -463,7 +463,7 @@ class HTTPMethodsHandler : public HTTPHandler { if (FindDiscGeometryFromMIMEType(&geometry, request.content_type.c_str(), request.body.size(), - &messages)) { + messages)) { // ok... } else if (!name.empty() && FindDiscGeometryFromFileDetails(&geometry, @@ -478,7 +478,7 @@ class HTTPMethodsHandler : public HTTPHandler { message_list->ClearMessages(); - std::shared_ptr disc_image = MemoryDiscImage::LoadFromBuffer(name, HTTP_DISC_IMAGE_LOAD_METHOD, request.body.data(), request.body.size(), geometry, &messages); + std::shared_ptr disc_image = MemoryDiscImage::LoadFromBuffer(name, HTTP_DISC_IMAGE_LOAD_METHOD, request.body.data(), request.body.size(), geometry, messages); if (!disc_image) { this->SendMessagesResponse(server, request, message_list); return nullptr; diff --git a/src/b2/MemoryDiscImage.cpp b/src/b2/MemoryDiscImage.cpp index 7951beb7..57670e13 100644 --- a/src/b2/MemoryDiscImage.cpp +++ b/src/b2/MemoryDiscImage.cpp @@ -9,9 +9,9 @@ #include #include #include "load_save.h" -#include "Messages.h" #include #include "native_ui.h" +#include #ifdef __GNUC__ #pragma GCC diagnostic push @@ -85,14 +85,14 @@ std::shared_ptr MemoryDiscImage::LoadFromBuffer( std::string load_method, const void *data, size_t data_size, const DiscGeometry &geometry, - Messages *msg) { + const LogSet &logs) { if (data_size == 0) { - msg->e.f("%s: disc image is empty\n", path.c_str()); + logs.e.f("%s: disc image is empty\n", path.c_str()); return nullptr; } if (data_size % geometry.bytes_per_sector != 0) { - msg->e.f("%s: not a multiple of sector size (%zu)\n", + logs.e.f("%s: not a multiple of sector size (%zu)\n", path.c_str(), geometry.bytes_per_sector); return nullptr; } @@ -110,7 +110,7 @@ static bool LoadDiscImageFromZipFile( std::vector *data, DiscGeometry *geometry, const std::string &zip_file_name, - Messages *msg) { + const LogSet &logs) { mz_zip_archive za; bool za_opened = 0; bool good = 0; @@ -121,7 +121,7 @@ static bool LoadDiscImageFromZipFile( // memset(&za, 0, sizeof za); if (!mz_zip_reader_init_file(&za, zip_file_name.c_str(), 0)) { - msg->e.f("failed to init zip file reader\n"); + logs.e.f("failed to init zip file reader\n"); goto done; } @@ -138,22 +138,22 @@ static bool LoadDiscImageFromZipFile( mz_zip_archive_file_stat stat; if (!mz_zip_reader_file_stat(&za, i, &stat)) { - msg->e.f("failed to get file info from zip file: %s\n", zip_file_name.c_str()); - msg->i.f("(problem file: %s)\n", name.data()); + logs.e.f("failed to get file info from zip file: %s\n", zip_file_name.c_str()); + logs.i.f("(problem file: %s)\n", name.data()); goto done; } if (stat.m_uncomp_size > SIZE_MAX) { - msg->e.f("file is too large in zip file: %s\n", zip_file_name.c_str()); - msg->i.f("(problem file: %s)\n", name.data()); + logs.e.f("file is too large in zip file: %s\n", zip_file_name.c_str()); + logs.i.f("(problem file: %s)\n", name.data()); goto done; } DiscGeometry g; if (FindDiscGeometryFromFileDetails(&g, name.data(), stat.m_uncomp_size, nullptr)) { if (image_index != BAD_INDEX) { - msg->e.f("zip file contains multiple disc images: %s\n", zip_file_name.c_str()); - msg->i.f("(at least: %s, %s)\n", name.data(), image_name->c_str()); + logs.e.f("zip file contains multiple disc images: %s\n", zip_file_name.c_str()); + logs.i.f("(at least: %s, %s)\n", name.data(), image_name->c_str()); goto done; } @@ -165,14 +165,14 @@ static bool LoadDiscImageFromZipFile( } if (image_index == BAD_INDEX) { - msg->e.f("zip file contains no disc images: %s\n", zip_file_name.c_str()); + logs.e.f("zip file contains no disc images: %s\n", zip_file_name.c_str()); goto done; } data->resize((size_t)image_stat.m_uncomp_size); if (!mz_zip_reader_extract_to_mem(&za, image_index, data->data(), data->size(), 0)) { - msg->e.f("failed to extract disc image from zip: %s\n", zip_file_name.c_str()); - msg->i.f("(disc image: %s)\n", image_name->c_str()); + logs.e.f("failed to extract disc image from zip: %s\n", zip_file_name.c_str()); + logs.i.f("(disc image: %s)\n", image_name->c_str()); goto done; } @@ -194,12 +194,12 @@ done:; static bool LoadDiscImage(std::vector *data, DiscGeometry *geometry, const std::string &path, - Messages *msg) { - if (!LoadFile(data, path, msg)) { + const LogSet &logs) { + if (!LoadFile(data, path, logs)) { return false; } - if (!FindDiscGeometryFromFileDetails(geometry, path.c_str(), data->size(), msg)) { + if (!FindDiscGeometryFromFileDetails(geometry, path.c_str(), data->size(), &logs)) { return false; } @@ -211,14 +211,14 @@ static bool LoadDiscImage(std::vector *data, std::shared_ptr MemoryDiscImage::LoadFromFile( std::string path, - Messages *msg) { + const LogSet &logs) { std::vector data; DiscGeometry geometry; std::string method; if (PathCompare(PathGetExtension(path), ".zip") == 0) { std::string name; - if (!LoadDiscImageFromZipFile(&name, &data, &geometry, path, msg)) { + if (!LoadDiscImageFromZipFile(&name, &data, &geometry, path, logs)) { return nullptr; } @@ -228,14 +228,14 @@ std::shared_ptr MemoryDiscImage::LoadFromFile( // later and rather unlikely to appear in a file name. path += "::" + name; } else { - if (!LoadDiscImage(&data, &geometry, path, msg)) { + if (!LoadDiscImage(&data, &geometry, path, logs)) { return nullptr; } method = LOAD_METHOD_FILE; } - return LoadFromBuffer(path, method, data.data(), data.size(), geometry, msg); + return LoadFromBuffer(path, method, data.data(), data.size(), geometry, logs); } ////////////////////////////////////////////////////////////////////////// diff --git a/src/b2/MemoryDiscImage.h b/src/b2/MemoryDiscImage.h index 1c6e676a..c132592f 100644 --- a/src/b2/MemoryDiscImage.h +++ b/src/b2/MemoryDiscImage.h @@ -8,8 +8,7 @@ #include #include "DiscGeometry.h" -class Messages; -class FileDialog; +struct LogSet; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -21,11 +20,11 @@ class MemoryDiscImage : public DiscImage { static const uint8_t FILL_BYTE; - static std::shared_ptr LoadFromBuffer(std::string path, std::string load_method, const void *data, size_t data_size, const DiscGeometry &geometry, Messages *msg); + static std::shared_ptr LoadFromBuffer(std::string path, std::string load_method, const void *data, size_t data_size, const DiscGeometry &geometry, const LogSet &logs); // If the load succeeds, the method will be LOAD_METHOD_FILE or // LOAD_METHOD_ZIP. - static std::shared_ptr LoadFromFile(std::string path, Messages *msg); + static std::shared_ptr LoadFromFile(std::string path, const LogSet &logs); ~MemoryDiscImage(); diff --git a/src/b2/b2.cpp b/src/b2/b2.cpp index b335a5e5..eb5f0efa 100644 --- a/src/b2/b2.cpp +++ b/src/b2/b2.cpp @@ -1373,9 +1373,9 @@ static bool main2(int argc, char *argv[], const std::shared_ptr &in } if (options.direct_disc[i]) { - ia.init_disc_images[i] = DirectDiscImage::CreateForFile(options.discs[i], &init_messages); + ia.init_disc_images[i] = DirectDiscImage::CreateForFile(options.discs[i], init_messages); } else { - ia.init_disc_images[i] = MemoryDiscImage::LoadFromFile(options.discs[i], &init_messages); + ia.init_disc_images[i] = MemoryDiscImage::LoadFromFile(options.discs[i], init_messages); } if (!ia.init_disc_images[i]) { From 639a3a05d41d55b080e0b9b9d5f3c634953c6301 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sat, 26 Oct 2024 16:57:37 +0100 Subject: [PATCH 05/31] Extern the DiscInterface objects. See #379. --- src/b2/BeebConfig.cpp | 18 +++++++++--------- src/beeb/include/beeb/DiscInterface.h | 11 +++++++++-- src/beeb/src/DiscInterface.cpp | 27 ++++++++++++++++----------- src/beeb/tests/test_common.cpp | 4 ++-- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/b2/BeebConfig.cpp b/src/b2/BeebConfig.cpp index 2da2ff43..9278d967 100644 --- a/src/b2/BeebConfig.cpp +++ b/src/b2/BeebConfig.cpp @@ -236,7 +236,7 @@ void InitDefaultBeebConfigs() { BeebConfig config; config.name = "B+"; - config.disc_interface = DISC_INTERFACE_ACORN_1770; + config.disc_interface = &DISC_INTERFACE_ACORN_1770; config.type_id = BBCMicroTypeID_BPlus; config.os.standard_rom = &BEEB_ROM_BPLUS_MOS; @@ -260,7 +260,7 @@ void InitDefaultBeebConfigs() { BeebConfig config; config.name = "Master 128 (MOS 3.20)"; - config.disc_interface = DISC_INTERFACE_MASTER128; + config.disc_interface = &DISC_INTERFACE_MASTER128; config.type_id = BBCMicroTypeID_Master; config.os.standard_rom = FindBeebROM(StandardROM_MOS320_MOS); config.roms[15].standard_rom = FindBeebROM(StandardROM_MOS320_TERMINAL); @@ -284,7 +284,7 @@ void InitDefaultBeebConfigs() { BeebConfig config; config.name = "Master 128 (MOS 3.50)"; - config.disc_interface = DISC_INTERFACE_MASTER128; + config.disc_interface = &DISC_INTERFACE_MASTER128; config.type_id = BBCMicroTypeID_Master; config.os.standard_rom = FindBeebROM(StandardROM_MOS350_MOS); config.roms[15].standard_rom = FindBeebROM(StandardROM_MOS350_TERMINAL); @@ -308,7 +308,7 @@ void InitDefaultBeebConfigs() { BeebConfig config; config.name = "Master Turbo (MOS 3.20)"; - config.disc_interface = DISC_INTERFACE_MASTER128; + config.disc_interface = &DISC_INTERFACE_MASTER128; config.type_id = BBCMicroTypeID_Master; config.os.standard_rom = FindBeebROM(StandardROM_MOS320_MOS); config.roms[15].standard_rom = FindBeebROM(StandardROM_MOS320_TERMINAL); @@ -335,7 +335,7 @@ void InitDefaultBeebConfigs() { BeebConfig config; config.name = "Master Turbo (MOS 3.50)"; - config.disc_interface = DISC_INTERFACE_MASTER128; + config.disc_interface = &DISC_INTERFACE_MASTER128; config.type_id = BBCMicroTypeID_Master; config.os.standard_rom = FindBeebROM(StandardROM_MOS350_MOS); config.roms[15].standard_rom = FindBeebROM(StandardROM_MOS350_TERMINAL); @@ -359,7 +359,7 @@ void InitDefaultBeebConfigs() { // BBC B with 6502 second processor { - BeebConfig config = GetBConfig(DISC_INTERFACE_ACORN_1770); + BeebConfig config = GetBConfig(&DISC_INTERFACE_ACORN_1770); config.name += " + 6502 second processor"; config.parasite_type = BBCMicroParasiteType_External3MHz6502; @@ -374,7 +374,7 @@ void InitDefaultBeebConfigs() { BeebConfig config; config.name = "Master Compact (MOS 5.00)"; - config.disc_interface = DISC_INTERFACE_MASTER128; + config.disc_interface = &DISC_INTERFACE_MASTER128; config.type_id = BBCMicroTypeID_MasterCompact; config.os.standard_rom = &BEEB_ROM_MOS500_MOS_ROM; config.roms[15].standard_rom = &BEEB_ROM_MOS500_SIDEWAYS_ROM_F; @@ -395,7 +395,7 @@ void InitDefaultBeebConfigs() { BeebConfig config; config.name = "Master Compact (MOS 5.10)"; - config.disc_interface = DISC_INTERFACE_MASTER128; + config.disc_interface = &DISC_INTERFACE_MASTER128; config.type_id = BBCMicroTypeID_MasterCompact; config.os.standard_rom = &BEEB_ROM_MOS510_MOS_ROM; config.roms[15].standard_rom = &BEEB_ROM_MOS510_SIDEWAYS_ROM_F; @@ -416,7 +416,7 @@ void InitDefaultBeebConfigs() { BeebConfig config; config.name = "Olivetti PC 128 S"; - config.disc_interface = DISC_INTERFACE_MASTER128; + config.disc_interface = &DISC_INTERFACE_MASTER128; config.type_id = BBCMicroTypeID_MasterCompact; config.os.standard_rom = &BEEB_ROM_MOSI510C_MOS_ROM; config.roms[15].standard_rom = &BEEB_ROM_MOSI510C_SIDEWAYS_ROM_F; diff --git a/src/beeb/include/beeb/DiscInterface.h b/src/beeb/include/beeb/DiscInterface.h index b54c0f98..db19966f 100644 --- a/src/beeb/include/beeb/DiscInterface.h +++ b/src/beeb/include/beeb/DiscInterface.h @@ -96,10 +96,17 @@ class DiscInterface { ////////////////////////////////////////////////////////////////////////// // This disc interface is used for the B+ and B+128. -extern const DiscInterface *const DISC_INTERFACE_ACORN_1770; +extern const DiscInterface &DISC_INTERFACE_ACORN_1770; // This disc interface is used for the Master 128. -extern const DiscInterface *const DISC_INTERFACE_MASTER128; +extern const DiscInterface &DISC_INTERFACE_MASTER128; + +// Other BBC B disk interfaces. +extern const DiscInterface &DISC_INTERFACE_WATFORD_DDB2; +extern const DiscInterface &DISC_INTERFACE_WATFORD_DDB3; +extern const DiscInterface &DISC_INTERFACE_OPUS; +extern const DiscInterface &DISC_INTERFACE_CHALLENGER_256K; +extern const DiscInterface &DISC_INTERFACE_CHALLENGER_512K; // The list of disc interfaces that can be used with a model B. Array // ends with NULL. diff --git a/src/beeb/src/DiscInterface.cpp b/src/beeb/src/DiscInterface.cpp index cbefe95f..3fad5cc9 100644 --- a/src/beeb/src/DiscInterface.cpp +++ b/src/beeb/src/DiscInterface.cpp @@ -159,7 +159,7 @@ class DiscInterfaceAcorn1770 : public DiscInterface { }; static const DiscInterfaceAcorn1770 DISC_INTERFACE_ACORN_1770_VALUE; -const DiscInterface *const DISC_INTERFACE_ACORN_1770 = &DISC_INTERFACE_ACORN_1770_VALUE; +const DiscInterface &DISC_INTERFACE_ACORN_1770 = DISC_INTERFACE_ACORN_1770_VALUE; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -209,7 +209,8 @@ class DiscInterfaceWatford1770DDB2 : public DiscInterface { private: }; -static const DiscInterfaceWatford1770DDB2 DISC_INTERFACE_WATFORD_DDB2; +static const DiscInterfaceWatford1770DDB2 DISC_INTERFACE_WATFORD_DDB2_VALUE; +const DiscInterface &DISC_INTERFACE_WATFORD_DDB2 = DISC_INTERFACE_WATFORD_DDB2_VALUE; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -262,7 +263,8 @@ class DiscInterfaceWatford1770DDB3 : public DiscInterface { private: }; -static const DiscInterfaceWatford1770DDB3 DISC_INTERFACE_WATFORD_DDB3; +static const DiscInterfaceWatford1770DDB3 DISC_INTERFACE_WATFORD_DDB3_VALUE; +const DiscInterface &DISC_INTERFACE_WATFORD_DDB3 = DISC_INTERFACE_WATFORD_DDB3_VALUE; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -306,7 +308,8 @@ class DiscInterfaceOpus1770 : public DiscInterface { private: }; -static const DiscInterfaceOpus1770 DISC_INTERFACE_OPUS; +static const DiscInterfaceOpus1770 DISC_INTERFACE_OPUS_VALUE; +const DiscInterface &DISC_INTERFACE_OPUS = DISC_INTERFACE_OPUS_VALUE; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -571,10 +574,12 @@ class DiscInterfaceChallenger : public DiscInterface { }; static const char CHALLENGER_256K_CONFIG_NAME[] = "Opus CHALLENGER 256K"; -static const DiscInterfaceChallenger DISC_INTERFACE_CHALLENGER_256K(CHALLENGER_256K_CONFIG_NAME, "Opus CHALLENGER 256K", 256 * 1024); +static const DiscInterfaceChallenger DISC_INTERFACE_CHALLENGER_256K_VALUE(CHALLENGER_256K_CONFIG_NAME, "Opus CHALLENGER 256K", 256 * 1024); +const DiscInterface &DISC_INTERFACE_CHALLENGER_256K = DISC_INTERFACE_CHALLENGER_256K_VALUE; static const char CHALLENGER_512K_CONFIG_NAME[] = "Opus CHALLENGER 512K"; -static const DiscInterfaceChallenger DISC_INTERFACE_CHALLENGER_512K(CHALLENGER_512K_CONFIG_NAME, "Opus CHALLENGER 512K", 512 * 1024); +static const DiscInterfaceChallenger DISC_INTERFACE_CHALLENGER_512K_VALUE(CHALLENGER_512K_CONFIG_NAME, "Opus CHALLENGER 512K", 512 * 1024); +const DiscInterface &DISC_INTERFACE_CHALLENGER_512K = DISC_INTERFACE_CHALLENGER_512K_VALUE; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -630,14 +635,14 @@ class DiscInterfaceMaster128 : public DiscInterface { private: }; -const DiscInterfaceMaster128 DISC_INTERFACE_MASTER128_VALUE; -const DiscInterface *const DISC_INTERFACE_MASTER128 = &DISC_INTERFACE_MASTER128_VALUE; +static const DiscInterfaceMaster128 DISC_INTERFACE_MASTER128_VALUE; +const DiscInterface &DISC_INTERFACE_MASTER128 = DISC_INTERFACE_MASTER128_VALUE; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// const DiscInterface *const MODEL_B_DISC_INTERFACES[] = { - &DISC_INTERFACE_ACORN_1770_VALUE, + &DISC_INTERFACE_ACORN_1770, &DISC_INTERFACE_WATFORD_DDB2, &DISC_INTERFACE_WATFORD_DDB3, &DISC_INTERFACE_OPUS, @@ -658,8 +663,8 @@ const DiscInterface *FindDiscInterfaceByConfigName(const char *config_name) { } } - if (DISC_INTERFACE_MASTER128->config_name == config_name) { - return DISC_INTERFACE_MASTER128; + if (DISC_INTERFACE_MASTER128.config_name == config_name) { + return &DISC_INTERFACE_MASTER128; } return nullptr; diff --git a/src/beeb/tests/test_common.cpp b/src/beeb/tests/test_common.cpp index a578ff8e..3c18f584 100644 --- a/src/beeb/tests/test_common.cpp +++ b/src/beeb/tests/test_common.cpp @@ -444,13 +444,13 @@ static const DiscInterface *GetDiscInterface(TestBBCMicroType type, uint32_t) { return nullptr; case TestBBCMicroType_BAcorn1770DFS: - return DISC_INTERFACE_ACORN_1770; + return &DISC_INTERFACE_ACORN_1770; case TestBBCMicroType_Master128MOS320: case TestBBCMicroType_Master128MOS350: case TestBBCMicroType_Master128MOS320WithMasterTurbo: case TestBBCMicroType_Master128MOS320WithExternal3MHz6502: - return DISC_INTERFACE_MASTER128; + return &DISC_INTERFACE_MASTER128; } } From 48ea08a343c68b3a8d9d3078e20d7a7b2e8eebbb Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sat, 26 Oct 2024 17:44:40 +0100 Subject: [PATCH 06/31] Shuffle some I/O stuff around a bit, to move bits into shared_lib. See #379. --- .../test_media_foundation.cpp | 3 +- src/6502/t/6502_basic_tests.cpp | 3 +- src/6502/t/klaus.cpp | 3 +- src/b2/BeebConfig.cpp | 4 +- src/b2/BeebWindow.cpp | 11 +- src/b2/CMakeLists.txt | 2 +- src/b2/DirectDiscImage.cpp | 12 +- src/b2/DiscGeometry.cpp | 12 +- src/b2/MemoryDiscImage.cpp | 5 +- src/b2/TraceUI.cpp | 2 +- src/b2/b2.cpp | 2 +- src/b2/debugger.cpp | 1 + src/b2/load_save.cpp | 241 +----------------- src/b2/load_save.h | 20 -- src/beeb/tests/test_common.cpp | 22 +- src/shared/CMakeLists.txt | 1 + src/shared/c/file_io.cpp | 191 ++++++++++++++ src/shared/c/path.cpp | 104 +------- src/shared/c/path_windows.cpp | 44 +++- src/shared/h/shared/file_io.h | 32 +++ .../h/shared/file_io.inl} | 0 src/shared/h/shared/path.h | 14 +- src/shared/t/test_path.cpp | 4 +- 23 files changed, 309 insertions(+), 424 deletions(-) create mode 100644 src/shared/c/file_io.cpp create mode 100644 src/shared/h/shared/file_io.h rename src/{b2/load_save.inl => shared/h/shared/file_io.inl} (100%) diff --git a/experimental/test_media_foundation/test_media_foundation.cpp b/experimental/test_media_foundation/test_media_foundation.cpp index 97fc55a0..56995cdd 100644 --- a/experimental/test_media_foundation/test_media_foundation.cpp +++ b/experimental/test_media_foundation/test_media_foundation.cpp @@ -18,6 +18,7 @@ #include "testing_windows.h" #include #include +#include #include #include "test_media_foundation.inl" @@ -174,7 +175,7 @@ static bool StoreWAVFile(const char *id, uint32_t size, const char *data, void * static bool LoadWAVFile(WAVFile *file, const std::string &file_name) { std::vector data; - if (!PathLoadBinaryFile(&data, file_name)) { + if (!LoadFile(&data, file_name, nullptr)) { return false; } diff --git a/src/6502/t/6502_basic_tests.cpp b/src/6502/t/6502_basic_tests.cpp index 6062cba6..13a90eba 100644 --- a/src/6502/t/6502_basic_tests.cpp +++ b/src/6502/t/6502_basic_tests.cpp @@ -6,6 +6,7 @@ #include #include #include +#include LOG_DEFINE(OUTPUT, "", &log_printer_stdout_and_debugger); @@ -21,7 +22,7 @@ static std::vector LoadFile(const std::string &dname, const std::string std::string path = PathJoined(dname, fname); std::vector data; - TEST_TRUE(PathLoadBinaryFile(&data, path)); + TEST_TRUE(LoadFile(&data, path, nullptr)); TEST_EQ_UU(data.size(), 65536); LOGF(OUTPUT, "%s: %zu\n", path.c_str(), data.size()); diff --git a/src/6502/t/klaus.cpp b/src/6502/t/klaus.cpp index de0ee88f..1d6111ba 100644 --- a/src/6502/t/klaus.cpp +++ b/src/6502/t/klaus.cpp @@ -6,6 +6,7 @@ #include #include #include +#include /* Test driver for Klaus Dormann's test suite, * https://github.com/Klaus2m5/6502_65C02_functional_tests */ @@ -29,7 +30,7 @@ static int DoTest(const std::string &fname, std::string path = PathJoined(KLAUS_FOLDER_NAME, fname); std::vector data; - TEST_TRUE(PathLoadBinaryFile(&data, path)); + TEST_TRUE(LoadFile(&data, path, nullptr)); TEST_TRUE(load_address + data.size() <= sizeof g_mem); memset(g_mem, 0, sizeof g_mem); diff --git a/src/b2/BeebConfig.cpp b/src/b2/BeebConfig.cpp index 9278d967..7435e3d2 100644 --- a/src/b2/BeebConfig.cpp +++ b/src/b2/BeebConfig.cpp @@ -6,12 +6,12 @@ #include #include "Messages.h" #include -#include #include #include "conf.h" #include "load_save.h" #include #include "misc.h" +#include #include #include "BeebConfig.inl" @@ -39,7 +39,7 @@ static bool LoadROM2(std::vector *data, const BeebConfig::ROM &rom, siz path = rom.file_name; } - if (!LoadFile(data, path, *msg)) { + if (!LoadFile(data, path, msg)) { return false; } diff --git a/src/b2/BeebWindow.cpp b/src/b2/BeebWindow.cpp index 3b53f521..6f00143d 100644 --- a/src/b2/BeebWindow.cpp +++ b/src/b2/BeebWindow.cpp @@ -45,6 +45,7 @@ #if SYSTEM_WINDOWS #include #endif +#include #ifdef _MSC_VER #include @@ -933,7 +934,7 @@ class FileMenuItem { if (ImGui::MenuItem(disc->name.c_str())) { std::string src_path = disc->GetAssetPath(); - if (!LoadFile(&this->new_disc_data, src_path, msgs, 0)) { + if (!LoadFile(&this->new_disc_data, src_path, msgs)) { return; } @@ -947,14 +948,14 @@ class FileMenuItem { } }; -static size_t CleanUpRecentPaths(const std::string &tag, bool (*exists_fn)(const std::string &)) { +static size_t CleanUpRecentPaths(const std::string &tag) { size_t n = 0; if (RecentPaths *rp = GetRecentPathsByTag(tag)) { size_t i = 0; while (i < rp->GetNumPaths()) { - if ((*exists_fn)(rp->GetPathByIndex(i))) { + if (PathIsFileOnDisk(rp->GetPathByIndex(i), nullptr, nullptr)) { ++i; } else { rp->RemovePathByIndex(i); @@ -1253,8 +1254,8 @@ void BeebWindow::DoCommands(bool *close_window) { if (m_cst.WasActioned(g_clean_up_recent_files_lists_command)) { size_t n = 0; - n += CleanUpRecentPaths(RECENT_PATHS_DISC_IMAGE, &PathIsFileOnDisk); - n += CleanUpRecentPaths(RECENT_PATHS_NVRAM, &PathIsFileOnDisk); + n += CleanUpRecentPaths(RECENT_PATHS_DISC_IMAGE); + n += CleanUpRecentPaths(RECENT_PATHS_NVRAM); if (n > 0) { m_msg.i.f("Removed %zu items\n", n); diff --git a/src/b2/CMakeLists.txt b/src/b2/CMakeLists.txt index 130500c1..8e49969b 100644 --- a/src/b2/CMakeLists.txt +++ b/src/b2/CMakeLists.txt @@ -46,7 +46,7 @@ add_executable(b2 MACOSX_BUNDLE filters.cpp filters.h keymap.cpp keymap.h keys.cpp keys.h keys.inl - load_save.cpp load_save.h load_save.inl + load_save.cpp load_save.h misc.cpp misc.h misc.inl native_ui.cpp native_ui.h roms.cpp roms.h diff --git a/src/b2/DirectDiscImage.cpp b/src/b2/DirectDiscImage.cpp index 234b0dcb..36e2af41 100644 --- a/src/b2/DirectDiscImage.cpp +++ b/src/b2/DirectDiscImage.cpp @@ -1,12 +1,12 @@ #include -#include #include #include "misc.h" #include "DirectDiscImage.h" #include "native_ui.h" #include "Messages.h" #include -#include "load_save.h" +#include +#include ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -27,9 +27,9 @@ DirectDiscImage::~DirectDiscImage() { std::shared_ptr DirectDiscImage::CreateForFile(std::string path, const LogSet &logs) { - size_t size; + uint64_t size; bool can_write; - if (!GetFileDetails(&size, &can_write, path.c_str())) { + if (!PathIsFileOnDisk(path,&size,&can_write)) { logs.e.f("Couldn't get details for file: %s\n", path.c_str()); return nullptr; } @@ -115,11 +115,11 @@ bool DirectDiscImage::SaveToFile(const std::string &file_name, const LogSet &log this->Close(); std::vector data; - if (!LoadFile(&data, m_path, logs)) { + if (!LoadFile(&data, m_path, &logs)) { return false; } - if (!SaveFile(data, file_name, logs)) { + if (!SaveFile(data, file_name, &logs)) { return false; } diff --git a/src/b2/DiscGeometry.cpp b/src/b2/DiscGeometry.cpp index 954fdc78..bef3d09c 100644 --- a/src/b2/DiscGeometry.cpp +++ b/src/b2/DiscGeometry.cpp @@ -53,7 +53,7 @@ static const size_t NUM_DDD_GEOMETRIES = sizeof DDD_GEOMETRIES / sizeof DDD_GEOM struct DiscImageType; -typedef bool (*FindDiscGeometryFn)(DiscGeometry *geometry, const char *name, size_t size, const LogSet *logs, const DiscImageType *disc_image_type); +typedef bool (*FindDiscGeometryFn)(DiscGeometry *geometry, const char *name, uint64_t size, const LogSet *logs, const DiscImageType *disc_image_type); struct DiscImageType { const char *ext; @@ -98,7 +98,7 @@ static bool IsMultipleOfSectorSize(const char *name, size_t size, const LogSet * return true; } -static bool FindSingleDensityDiscGeometry(DiscGeometry *geometry, const char *name, size_t size, const LogSet *logs, const DiscImageType *disc_image_type) { +static bool FindSingleDensityDiscGeometry(DiscGeometry *geometry, const char *name, uint64_t size, const LogSet *logs, const DiscImageType *disc_image_type) { ASSERT(disc_image_type->num_possible_geometries == 1); ASSERT(!disc_image_type->possible_geometries[0].double_density); *geometry = DiscGeometry(80, 10, 256, disc_image_type->possible_geometries[0].double_sided); @@ -124,7 +124,7 @@ static bool FindSingleDensityDiscGeometry(DiscGeometry *geometry, const char *na return true; } -static bool FindDiscGeometryFromFileSize(DiscGeometry *geometry, const char *name, size_t size, const LogSet *logs, const DiscImageType *disc_image_type) { +static bool FindDiscGeometryFromFileSize(DiscGeometry *geometry, const char *name, uint64_t size, const LogSet *logs, const DiscImageType *disc_image_type) { for (size_t i = 0; i < disc_image_type->num_possible_geometries; ++i) { const DiscGeometry *g = &disc_image_type->possible_geometries[i]; @@ -151,7 +151,7 @@ static bool FindDiscGeometryFromFileSize(DiscGeometry *geometry, const char *nam return false; } -static bool FindADFSDiscGeometry(DiscGeometry *geometry, const char *name, size_t size, const LogSet *logs, const DiscImageType *disc_image_type) { +static bool FindADFSDiscGeometry(DiscGeometry *geometry, const char *name, uint64_t size, const LogSet *logs, const DiscImageType *disc_image_type) { (void)disc_image_type; if (!IsMultipleOfSectorSize(name, size, logs)) { @@ -313,7 +313,7 @@ bool operator!=(const DiscGeometry &a, const DiscGeometry &b) { bool FindDiscGeometryFromFileDetails(DiscGeometry *geometry, const char *file_name, - size_t file_size, + uint64_t file_size, const LogSet *logs) { std::string ext = PathGetExtension(file_name); @@ -335,7 +335,7 @@ bool FindDiscGeometryFromFileDetails(DiscGeometry *geometry, bool FindDiscGeometryFromMIMEType(DiscGeometry *geometry, const char *mime_type, - size_t file_size, + uint64_t file_size, const LogSet &logs) { for (const DiscImageType *type = DISC_IMAGE_TYPES; type->ext; ++type) { if (strcmp(mime_type, type->mime_type) == 0) { diff --git a/src/b2/MemoryDiscImage.cpp b/src/b2/MemoryDiscImage.cpp index 57670e13..5d25ccfa 100644 --- a/src/b2/MemoryDiscImage.cpp +++ b/src/b2/MemoryDiscImage.cpp @@ -12,6 +12,7 @@ #include #include "native_ui.h" #include +#include #ifdef __GNUC__ #pragma GCC diagnostic push @@ -195,7 +196,7 @@ static bool LoadDiscImage(std::vector *data, DiscGeometry *geometry, const std::string &path, const LogSet &logs) { - if (!LoadFile(data, path, logs)) { + if (!LoadFile(data, path, &logs)) { return false; } @@ -364,7 +365,7 @@ std::vector MemoryDiscImage::GetFileDialogFilters() const { ////////////////////////////////////////////////////////////////////////// bool MemoryDiscImage::SaveToFile(const std::string &file_name, const LogSet &logs) const { - return SaveFile(m_data->data, file_name, logs); + return SaveFile(m_data->data, file_name, &logs); } ////////////////////////////////////////////////////////////////////////// diff --git a/src/b2/TraceUI.cpp b/src/b2/TraceUI.cpp index 957feda0..ff137de8 100644 --- a/src/b2/TraceUI.cpp +++ b/src/b2/TraceUI.cpp @@ -16,7 +16,7 @@ #include #include #include "SettingsUI.h" -#include +#include #include #include "TraceUI.inl" diff --git a/src/b2/b2.cpp b/src/b2/b2.cpp index eb5f0efa..6d7ff3f7 100644 --- a/src/b2/b2.cpp +++ b/src/b2/b2.cpp @@ -699,7 +699,7 @@ static bool ParseCommandLineOptions( // Detect the File Explorer double-click case on Windows (also acts as a // convenient command-line shortcut). - if (argc == 2 && PathIsFileOnDisk(argv[1])) { + if (argc == 2 && PathIsFileOnDisk(argv[1], nullptr, nullptr)) { options->file_association_mode = true; options->file_association_path = argv[1]; diff --git a/src/b2/debugger.cpp b/src/b2/debugger.cpp index e860f2a4..00fa761f 100644 --- a/src/b2/debugger.cpp +++ b/src/b2/debugger.cpp @@ -4,6 +4,7 @@ #include #include "joysticks.h" #include "SettingsUI.h" +#include ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// diff --git a/src/b2/load_save.cpp b/src/b2/load_save.cpp index e882e5e2..7dd1d4a8 100644 --- a/src/b2/load_save.cpp +++ b/src/b2/load_save.cpp @@ -27,6 +27,7 @@ #include "b2.h" #include "BeebLinkHTTPHandler.h" #include "joysticks.h" +#include #ifdef __GNUC__ #pragma GCC diagnostic push @@ -42,10 +43,6 @@ #pragma GCC diagnostic pop #endif -#include -#include "load_save.inl" -#include - ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -378,195 +375,6 @@ static bool GetDataFromHexString(std::vector *data, const std::string & ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -static void AddError(const LogSet &logs, - const std::string &path, - const char *what1, - const char *what2, - int err) { - logs.w.f("%s failed: %s\n", what1, path.c_str()); - - if (err != 0) { - logs.i.f("(%s: %s)\n", what2, strerror(err)); - } else { - logs.i.f("(%s)\n", what2); - } -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -template -static bool LoadFile2(ContType *data, - const std::string &path, - const LogSet &logs, - uint32_t flags, - const char *mode) { - static_assert(sizeof(typename ContType::value_type) == 1, "LoadFile2 can only load into a vector of bytes"); - FILE *f = NULL; - bool good = false; - long len; - size_t num_bytes, num_read; - - f = fopenUTF8(path.c_str(), mode); - if (!f) { - if (errno == ENOENT && (flags & LoadFlag_MightNotExist)) { - // ignore this error. - } else { - AddError(logs, path, "load", "open failed", errno); - } - - goto done; - } - - if (fseek(f, 0, SEEK_END) == -1) { - AddError(logs, path, "load", "fseek (1) failed", errno); - goto done; - } - - len = ftell(f); - if (len < 0) { - AddError(logs, path, "load", "ftell failed", errno); - goto done; - } - -#if LONG_MAX > SIZE_MAX - if (len > (long)SIZE_MAX) { - AddError(logs, path, "load", "file is too large", 0); - goto done; - } -#endif - - if (fseek(f, 0, SEEK_SET) == -1) { - AddError(logs, path, "load", "fseek (2) failed", errno); - goto done; - } - - num_bytes = (size_t)len; - data->resize(num_bytes); - - num_read = fread(data->data(), 1, num_bytes, f); - if (ferror(f)) { - AddError(logs, path, "load", "read failed", errno); - goto done; - } - - // Number of bytes read may be smaller if mode is rt. - data->resize(num_read); - good = true; - -done:; - if (!good) { - data->clear(); - } - - if (f) { - fclose(f); - f = NULL; - } - - return good; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -bool LoadFile(std::vector *data, const std::string &path, const LogSet &logs, uint32_t flags) { - if (!LoadFile2(data, path, logs, flags, "rb")) { - return false; - } - - data->shrink_to_fit(); - return true; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -bool LoadFile(std::vector *data, const std::string &path, Messages *messages, uint32_t flags) { - return LoadFile(data, path, *messages, flags); -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -bool LoadTextFile(std::vector *data, const std::string &path, const LogSet &logs, uint32_t flags) { - if (!LoadFile2(data, path, logs, flags, "rt")) { - return false; - } - - data->push_back(0); - return true; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -bool LoadTextFile(std::vector *data, const std::string &path, Messages *messages, uint32_t flags) { - return LoadTextFile(data, path, *messages, flags); -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -static bool SaveFile2(const void *data, size_t data_size, const std::string &path, const LogSet &logs, const char *fopen_mode) { - FILE *f = fopenUTF8(path.c_str(), fopen_mode); - if (!f) { - AddError(logs, path, "save", "fopen failed", errno); - return false; - } - - fwrite(data, 1, data_size, f); - - bool bad = !!ferror(f); - int e = errno; - - fclose(f); - f = nullptr; - - if (bad) { - AddError(logs, path, "save", "write failed", e); - return false; - } - - return true; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -bool SaveFile(const void *data, size_t data_size, const std::string &path, const LogSet &logs) { - return SaveFile2(data, data_size, path, logs, "wb"); -} - -bool SaveFile(const void *data, size_t data_size, const std::string &path, Messages *messages) { - return SaveFile(data, data_size, path, *messages); -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -bool SaveFile(const std::vector &data, const std::string &path, const LogSet &logs) { - return SaveFile(data.data(), data.size(), path, logs); -} - -bool SaveFile(const std::vector &data, const std::string &path, Messages *messages) { - return SaveFile(data, path, *messages); -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -bool SaveTextFile(const std::string &data, const std::string &path, const LogSet &logs) { - return SaveFile2(data.c_str(), data.size(), path, logs, "wt"); -} - -bool SaveTextFile(const std::string &data, const std::string &path, Messages *messages) { - return SaveTextFile(data, path, *messages); -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - static bool IsR8G8B8(const SDL_PixelFormat *format) { if (format->Rmask != (0xff << 0)) { return false; @@ -649,53 +457,6 @@ unsigned char *SaveSDLSurfaceToPNGData(SDL_Surface *surface, size_t *png_size_ou ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -bool GetFileDetails(size_t *size, bool *can_write, const char *path) { - FILE *fp = nullptr; - bool good = false; - long len; - - fp = fopenUTF8(path, "r+b"); - if (fp) { - *can_write = true; - } else { - // doesn't exist, or read-only. - fp = fopenUTF8(path, "rb"); - if (!fp) { - // assume doesn't exist. - goto done; - } - - *can_write = false; - } - - if (fseek(fp, 0, SEEK_END) != 0) { - goto done; - } - - len = ftell(fp); - if (len < 0) { - goto done; - } - - if ((unsigned long)len > SIZE_MAX) { - *size = SIZE_MAX; - } else { - *size = (size_t)len; - } - - good = true; -done: - if (fp) { - fclose(fp); - fp = nullptr; - } - - return good; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - // RapidJSON-friendly stream class that writes its output into a // std::string. diff --git a/src/b2/load_save.h b/src/b2/load_save.h index 7fd19b10..72ee5ff9 100644 --- a/src/b2/load_save.h +++ b/src/b2/load_save.h @@ -17,10 +17,6 @@ struct LogSet; class Messages; struct SDL_Surface; -#include -#include "load_save.inl" -#include - ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -52,27 +48,11 @@ std::string GetCachePath(const std::string &path); // File names are assumed to be UTF-8. -bool LoadFile(std::vector *data, const std::string &path, const LogSet &logs, uint32_t flags = 0); -bool LoadFile(std::vector *data, const std::string &path, Messages *messages, uint32_t flags = 0); - -bool LoadTextFile(std::vector *data, const std::string &path, const LogSet &logs, uint32_t flags = 0); -bool LoadTextFile(std::vector *data, const std::string &path, Messages *messages, uint32_t flags = 0); - -bool SaveFile(const void *data, size_t data_size, const std::string &path, const LogSet &logs); -bool SaveFile(const void *data, size_t data_size, const std::string &path, Messages *messages); - -bool SaveFile(const std::vector &data, const std::string &path, const LogSet &logs); -bool SaveFile(const std::vector &data, const std::string &path, Messages *messages); - -bool SaveTextFile(const std::string &data, const std::string &path, const LogSet &logs); -bool SaveTextFile(const std::string &data, const std::string &path, Messages *messages); - bool SaveSDLSurface(SDL_Surface *surface, const std::string &path, Messages *messages); // Free result using free. unsigned char *SaveSDLSurfaceToPNGData(SDL_Surface *surface, size_t *png_size_out, Messages *messages); -bool GetFileDetails(size_t *size, bool *can_write, const char *path); ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// diff --git a/src/beeb/tests/test_common.cpp b/src/beeb/tests/test_common.cpp index 3c18f584..5b079cce 100644 --- a/src/beeb/tests/test_common.cpp +++ b/src/beeb/tests/test_common.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #ifdef __GNUC__ #pragma GCC diagnostic push @@ -391,7 +392,7 @@ static std::shared_ptr> LoadOSROM(const std::st std::string path = PathJoined(ROMS_FOLDER, name); std::vector data; - TEST_TRUE(PathLoadBinaryFile(&data, path)); + TEST_TRUE(LoadFile(&data, path, nullptr)); auto rom = std::make_shared>(); @@ -612,7 +613,7 @@ void TestBBCMicro::StopCaptureOSWRCH() { void TestBBCMicro::LoadFile(const std::string &path, uint32_t addr) { std::vector contents; - TEST_TRUE(PathLoadBinaryFile(&contents, path)); + TEST_TRUE(::LoadFile(&contents, path, nullptr)); if (this->GetParasiteType() == BBCMicroParasiteType_None || (addr & 0xffff0000) == 0xffff0000) { addr &= 0xffff; @@ -632,7 +633,7 @@ void TestBBCMicro::LoadFile(const std::string &path, uint32_t addr) { void TestBBCMicro::LoadSSD(int drive, const std::string &path) { std::vector contents; - TEST_TRUE(PathLoadBinaryFile(&contents, path)); + TEST_TRUE(::LoadFile(&contents, path, nullptr)); this->SetDiscImage(drive, std::make_shared(path, std::move(contents))); } @@ -864,7 +865,7 @@ void TestBBCMicro::LoadParasiteOS(const std::string &name) { std::string path = PathJoined(ROMS_FOLDER, name); std::vector data; - TEST_TRUE(PathLoadBinaryFile(&data, path)); + TEST_TRUE(::LoadFile(&data, path, nullptr)); auto rom = std::make_shared>(); @@ -1074,10 +1075,11 @@ void TestSpooledOutput(const TestBBCMicro &bbc, } std::vector wanted_results; - TEST_TRUE(PathLoadBinaryFile(&wanted_results, - GetTestFileName(beeblink_volume_path, - beeblink_drive, - bbc.spool_output_name))); + TEST_TRUE(LoadFile(&wanted_results, + GetTestFileName(beeblink_volume_path, + beeblink_drive, + bbc.spool_output_name), + nullptr)); std::string wanted_output(wanted_results.begin(), wanted_results.end()); @@ -1189,8 +1191,8 @@ void RunImageTest(const std::string &wanted_png_src_path, // The differences PNG isn't always illuminating. std::string wanted_png_path = GetOutputFileName(png_name + ".wanted.png"); std::vector wanted_png_data; - TEST_TRUE(PathLoadBinaryFile(&wanted_png_data, wanted_png_src_path)); - TEST_TRUE(PathSaveBinaryFile(wanted_png_data, wanted_png_path)); + TEST_TRUE(LoadFile(&wanted_png_data, wanted_png_src_path, nullptr)); + TEST_TRUE(SaveFile(wanted_png_data, wanted_png_path, nullptr)); int wanted_width, wanted_height; unsigned char *wanted_data = stbi_load(wanted_png_path.c_str(), diff --git a/src/shared/CMakeLists.txt b/src/shared/CMakeLists.txt index 860cbcfc..1638679f 100644 --- a/src/shared/CMakeLists.txt +++ b/src/shared/CMakeLists.txt @@ -31,6 +31,7 @@ set(SRCS c/relacy.cpp ${H}/relacy.h c/mutex.cpp ${H}/mutex.h ${H}/mutex.inl ${H}/pshpack1.h ${H}/pshpack4.h ${H}/pshpack8.h ${H}/poppack.h + c/file_io.cpp ${H}/file_io.h ${H}/file_io.inl ) if(APPLE) diff --git a/src/shared/c/file_io.cpp b/src/shared/c/file_io.cpp new file mode 100644 index 00000000..7f0a06d3 --- /dev/null +++ b/src/shared/c/file_io.cpp @@ -0,0 +1,191 @@ +#include +#include +#include +#include + +#include +#include +#include + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +FILE *fopenUTF8(const char *path, const char *mode) { +#if SYSTEM_WINDOWS + + return _wfopen(GetWideString(path).c_str(), GetWideString(mode).c_str()); + +#else + + return fopen(path, mode); //non-UTF8 ok + +#endif +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +static void AddError(const LogSet *logs, + const std::string &path, + const char *what1, + const char *what2, + int err) { + if (logs) { + logs->w.f("%s failed: %s\n", what1, path.c_str()); + + if (err != 0) { + logs->i.f("(%s: %s)\n", what2, strerror(err)); + } else { + logs->i.f("(%s)\n", what2); + } + } +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +template +static bool LoadFile2(ContType *data, + const std::string &path, + const LogSet *logs, + uint32_t flags, + const char *mode) { + static_assert(sizeof(typename ContType::value_type) == 1, "LoadFile2 can only load into a vector of bytes"); + FILE *f = NULL; + bool good = false; + long len; + size_t num_bytes, num_read; + + f = fopenUTF8(path.c_str(), mode); + if (!f) { + if (errno == ENOENT && (flags & LoadFlag_MightNotExist)) { + // ignore this error. + } else { + AddError(logs, path, "load", "open failed", errno); + } + + goto done; + } + + if (fseek(f, 0, SEEK_END) == -1) { + AddError(logs, path, "load", "fseek (1) failed", errno); + goto done; + } + + len = ftell(f); + if (len < 0) { + AddError(logs, path, "load", "ftell failed", errno); + goto done; + } + +#if LONG_MAX > SIZE_MAX + if (len > (long)SIZE_MAX) { + AddError(logs, path, "load", "file is too large", 0); + goto done; + } +#endif + + if (fseek(f, 0, SEEK_SET) == -1) { + AddError(logs, path, "load", "fseek (2) failed", errno); + goto done; + } + + num_bytes = (size_t)len; + data->resize(num_bytes); + + num_read = fread(data->data(), 1, num_bytes, f); + if (ferror(f)) { + AddError(logs, path, "load", "read failed", errno); + goto done; + } + + // Number of bytes read may be smaller if mode is rt. + data->resize(num_read); + good = true; + +done:; + if (!good) { + data->clear(); + } + + if (f) { + fclose(f); + f = NULL; + } + + return good; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +static bool SaveFile2(const void *data, size_t data_size, const std::string &path, const LogSet *logs, const char *fopen_mode) { + FILE *f = fopenUTF8(path.c_str(), fopen_mode); + if (!f) { + AddError(logs, path, "save", "fopen failed", errno); + return false; + } + + fwrite(data, 1, data_size, f); + + bool bad = !!ferror(f); + int e = errno; + + fclose(f); + f = nullptr; + + if (bad) { + AddError(logs, path, "save", "write failed", e); + return false; + } + + return true; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +bool LoadFile(std::vector *data, const std::string &path, const LogSet *logs, uint32_t flags) { + if (!LoadFile2(data, path, logs, flags, "rb")) { + return false; + } + + data->shrink_to_fit(); + return true; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +bool LoadTextFile(std::vector *data, const std::string &path, const LogSet*logs, uint32_t flags) { + if (!LoadFile2(data, path, logs, flags, "rt")) { + return false; + } + + data->push_back(0); + return true; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +bool SaveFile(const void *data, size_t data_size, const std::string &path, const LogSet *logs) { + return SaveFile2(data, data_size, path, logs, "wb"); +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +bool SaveFile(const std::vector &data, const std::string &path, const LogSet *logs) { + return SaveFile(data.data(), data.size(), path, logs); +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +bool SaveTextFile(const std::string &data, const std::string &path, const LogSet *logs) { + return SaveFile2(data.c_str(), data.size(), path, logs, "wt"); +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// diff --git a/src/shared/c/path.cpp b/src/shared/c/path.cpp index a61aa228..3de89417 100644 --- a/src/shared/c/path.cpp +++ b/src/shared/c/path.cpp @@ -154,6 +154,8 @@ int PathCompare(const std::string &a, const std::string &b) { int cb = b[bi]; #if SYSTEM_OSX || SYSTEM_WINDOWS + // This is very lazy, but this function is never used anywhere where it + // would be a problem. ca = tolower(ca); cb = tolower(cb); #endif @@ -183,105 +185,3 @@ int PathCompare(const std::string &a, const std::string &b) { ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// - -bool PathLoadBinaryFile(std::vector *contents, const std::string &path) { - FILE *f = NULL; - bool good = false; - size_t num_read; - long len; - - f = fopenUTF8(path.c_str(), "rb"); - if (!f) { - goto done; - } - - if (fseek(f, 0, SEEK_END) == -1) { - goto done; - } - - len = ftell(f); - if (len < 0) { - goto done; - } - -#if LONG_MAX > SIZE_MAX - if (len > (long)SIZE_MAX) { - goto done; - } -#endif - - if (fseek(f, 0, SEEK_SET) == -1) { - goto done; - } - - contents->resize((size_t)len); - - num_read = fread(contents->data(), 1, contents->size(), f); - if (num_read != contents->size()) { - goto done; - } - - good = true; - -done:; - int e = errno; - - if (f) { - fclose(f); - f = NULL; - } - - if (!good) { - contents->clear(); //may as well... - } - - errno = e; - return good; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -bool PathSaveBinaryFile(const std::vector &contents, const std::string &path) { - FILE *f = nullptr; - bool good = false; - - f = fopenUTF8(path.c_str(), "wb"); - if (!f) { - goto done; - } - - fwrite(contents.data(), 1, contents.size(), f); - - if (ferror(f)) { - goto done; - } - - good = true; - -done: - if (f) { - fclose(f); - f = nullptr; - } - - return good; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -FILE *fopenUTF8(const char *path, const char *mode) { -#if SYSTEM_WINDOWS - - return _wfopen(GetWideString(path).c_str(), GetWideString(mode).c_str()); - -#else - - return fopen(path, mode); //non-UTF8 ok - -#endif -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// diff --git a/src/shared/c/path_windows.cpp b/src/shared/c/path_windows.cpp index f5678dea..5ba4b781 100644 --- a/src/shared/c/path_windows.cpp +++ b/src/shared/c/path_windows.cpp @@ -47,9 +47,8 @@ bool PathGlob(const std::string &folder, std::function +#include +#include + +#include "enum_decl.h" +#include "file_io.inl" +#include "enum_end.h" + +struct LogSet; + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +FILE *fopenUTF8(const char *path, const char *mode); + +bool LoadFile(std::vector *data, const std::string &path, const LogSet *logs, uint32_t flags = 0); +bool LoadTextFile(std::vector *data, const std::string &path, const LogSet *logs, uint32_t flags = 0); + +bool SaveFile(const void *data, size_t data_size, const std::string &path, const LogSet *logs); +bool SaveFile(const std::vector &data, const std::string &path, const LogSet *logs); +bool SaveTextFile(const std::string &data, const std::string &path, const LogSet *logs); + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/src/b2/load_save.inl b/src/shared/h/shared/file_io.inl similarity index 100% rename from src/b2/load_save.inl rename to src/shared/h/shared/file_io.inl diff --git a/src/shared/h/shared/path.h b/src/shared/h/shared/path.h index 0c5013c0..445e236c 100644 --- a/src/shared/h/shared/path.h +++ b/src/shared/h/shared/path.h @@ -87,27 +87,15 @@ bool PathGlob(const std::string &folder, std::function *contents, const std::string &path); -bool PathSaveBinaryFile(const std::vector &contents, const std::string &path); - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - bool PathCreateFolder(const std::string &name); ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -FILE *fopenUTF8(const char *path, const char *mode); - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - #endif diff --git a/src/shared/t/test_path.cpp b/src/shared/t/test_path.cpp index 4c37e38e..b979639b 100644 --- a/src/shared/t/test_path.cpp +++ b/src/shared/t/test_path.cpp @@ -56,10 +56,10 @@ int main(int argc, char *argv[]) { TEST_TRUE(argc >= 3); TEST_TRUE(PathIsFolderOnDisk(argv[2])); - TEST_FALSE(PathIsFileOnDisk(argv[2])); + TEST_FALSE(PathIsFileOnDisk(argv[2], nullptr, nullptr)); std::string file = PathJoined(argv[2], PathGetName(__FILE__)); - TEST_TRUE(PathIsFileOnDisk(file)); + TEST_TRUE(PathIsFileOnDisk(file, nullptr, nullptr)); TEST_FALSE(PathIsFolderOnDisk(file)); } From 2b252bfe58acf3f1106d4bc052158bb1758d311e Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sat, 26 Oct 2024 17:46:31 +0100 Subject: [PATCH 07/31] clang-format. --- src/b2/DirectDiscImage.cpp | 2 +- src/b2/VBlankMonitorWindows.cpp | 2 +- src/b2/load_save.h | 1 - src/shared/c/file_io.cpp | 2 +- src/shared/h/shared/file_io.h | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/b2/DirectDiscImage.cpp b/src/b2/DirectDiscImage.cpp index 36e2af41..77ee1fec 100644 --- a/src/b2/DirectDiscImage.cpp +++ b/src/b2/DirectDiscImage.cpp @@ -29,7 +29,7 @@ std::shared_ptr DirectDiscImage::CreateForFile(std::string path const LogSet &logs) { uint64_t size; bool can_write; - if (!PathIsFileOnDisk(path,&size,&can_write)) { + if (!PathIsFileOnDisk(path, &size, &can_write)) { logs.e.f("Couldn't get details for file: %s\n", path.c_str()); return nullptr; } diff --git a/src/b2/VBlankMonitorWindows.cpp b/src/b2/VBlankMonitorWindows.cpp index 929d53ac..67bf8145 100644 --- a/src/b2/VBlankMonitorWindows.cpp +++ b/src/b2/VBlankMonitorWindows.cpp @@ -185,7 +185,7 @@ class VBlankMonitorWindows : public VBlankMonitor { // ms/frame). // // Each ThreadVBlank call represents a fair amount of work, and 250 - // Hz isn't ideal, so this does need revisiting. + // Hz isn't ideal, so this does need revisiting. uint64_t start_ticks = GetCurrentTickCount(); uint64_t wait_ticks; diff --git a/src/b2/load_save.h b/src/b2/load_save.h index 72ee5ff9..cbac7982 100644 --- a/src/b2/load_save.h +++ b/src/b2/load_save.h @@ -53,7 +53,6 @@ bool SaveSDLSurface(SDL_Surface *surface, const std::string &path, Messages *mes // Free result using free. unsigned char *SaveSDLSurfaceToPNGData(SDL_Surface *surface, size_t *png_size_out, Messages *messages); - ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// diff --git a/src/shared/c/file_io.cpp b/src/shared/c/file_io.cpp index 7f0a06d3..795ddf2d 100644 --- a/src/shared/c/file_io.cpp +++ b/src/shared/c/file_io.cpp @@ -157,7 +157,7 @@ bool LoadFile(std::vector *data, const std::string &path, const LogSet ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -bool LoadTextFile(std::vector *data, const std::string &path, const LogSet*logs, uint32_t flags) { +bool LoadTextFile(std::vector *data, const std::string &path, const LogSet *logs, uint32_t flags) { if (!LoadFile2(data, path, logs, flags, "rt")) { return false; } diff --git a/src/shared/h/shared/file_io.h b/src/shared/h/shared/file_io.h index e080280a..0ab157d3 100644 --- a/src/shared/h/shared/file_io.h +++ b/src/shared/h/shared/file_io.h @@ -1,4 +1,4 @@ -#ifndef HEADER_4466BEFE653742FAA5F44358E1E758AB// -*- mode:c++ -*- +#ifndef HEADER_4466BEFE653742FAA5F44358E1E758AB // -*- mode:c++ -*- #define HEADER_4466BEFE653742FAA5F44358E1E758AB ////////////////////////////////////////////////////////////////////////// From 9be709ec18873caa1a3b8343e97096a1b8cb9682 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sat, 26 Oct 2024 18:11:28 +0100 Subject: [PATCH 08/31] Fix macOS build. --- .../test_AVFoundation/test_AVFoundation.mm | 3 +- src/b2/DiscGeometry.cpp | 7 ++-- src/b2/DiscGeometry.h | 4 +-- src/shared/c/path_posix.cpp | 34 ++++++++++++++----- src/shared/h/shared/path.h | 1 + 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/experimental/test_AVFoundation/test_AVFoundation.mm b/experimental/test_AVFoundation/test_AVFoundation.mm index 117c2ac0..9016f26b 100644 --- a/experimental/test_AVFoundation/test_AVFoundation.mm +++ b/experimental/test_AVFoundation/test_AVFoundation.mm @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "test_AVFoundation.inl" @@ -158,7 +159,7 @@ static bool StoreWAVFile(const char *id, uint32_t size, const char *data, void * static bool LoadWAVFile(WAVFile *file, const std::string &file_name) { std::vector data; - if (!PathLoadBinaryFile(&data, file_name)) { + if (!LoadFile(&data, file_name, nullptr)) { return false; } diff --git a/src/b2/DiscGeometry.cpp b/src/b2/DiscGeometry.cpp index bef3d09c..d84289b4 100644 --- a/src/b2/DiscGeometry.cpp +++ b/src/b2/DiscGeometry.cpp @@ -5,6 +5,7 @@ #include #include "native_ui.h" #include +#include ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -111,7 +112,7 @@ static bool FindSingleDensityDiscGeometry(DiscGeometry *geometry, const char *na if (logs) { PrintInvalidSizeMessage(name, *logs); - logs->i.f("(size %zu bytes is larger than maximum %zu bytes for %d*%zu*%zu sectors)\n", + logs->i.f("(size %" PRIu64 " bytes is larger than maximum %zu bytes for %d*%zu*%zu sectors)\n", size, geometry->GetTotalNumBytes(), geometry->double_sided ? 2 : 1, @@ -137,7 +138,7 @@ static bool FindDiscGeometryFromFileSize(DiscGeometry *geometry, const char *nam if (logs) { PrintInvalidSizeMessage(name, *logs); - logs->i.f("(size is %zu; valid sizes are: ", size); + logs->i.f("(size is %" PRIu64 "; valid sizes are: ", size); for (size_t i = 0; i < disc_image_type->num_possible_geometries; ++i) { if (i > 0) { logs->i.f("; "); @@ -162,7 +163,7 @@ static bool FindADFSDiscGeometry(DiscGeometry *geometry, const char *name, uint6 if (logs) { PrintInvalidSizeMessage(name, *logs); - logs->i.f("(size is %zu; max valid size is %zu", size, ADL_SIZE); + logs->i.f("(size is %" PRIu64 "; max valid size is %zu", size, ADL_SIZE); } return false; diff --git a/src/b2/DiscGeometry.h b/src/b2/DiscGeometry.h index 84b5882f..7395db66 100644 --- a/src/b2/DiscGeometry.h +++ b/src/b2/DiscGeometry.h @@ -57,12 +57,12 @@ extern const std::vector DISC_IMAGE_EXTENSIONS; bool FindDiscGeometryFromFileDetails(DiscGeometry *geometry, const char *file_name, - size_t file_size, + uint64_t file_size, const LogSet *logs); bool FindDiscGeometryFromMIMEType(DiscGeometry *geometry, const char *mime_type, - size_t file_size, + uint64_t file_size, const LogSet &logs); ////////////////////////////////////////////////////////////////////////// diff --git a/src/shared/c/path_posix.cpp b/src/shared/c/path_posix.cpp index 6f7cf6c5..5866bd11 100644 --- a/src/shared/c/path_posix.cpp +++ b/src/shared/c/path_posix.cpp @@ -6,6 +6,7 @@ #include #include #include +#include ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -37,25 +38,42 @@ bool PathGlob(const std::string &folder, ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -static bool IsThingOnDisk(const std::string &path, mode_t mask) { +bool PathIsFileOnDisk(const std::string &path, uint64_t *file_size, bool *can_write) { struct stat st; if (stat(path.c_str(), &st) == -1) { return false; } - if ((st.st_mode & mask) == mask) { - return true; - } else { + if ((st.st_mode & S_IFREG) != S_IFREG) { return false; } -} -bool PathIsFileOnDisk(const std::string &path) { - return IsThingOnDisk(path, S_IFREG); + if (file_size) { + if (st.st_size < 0) { + *file_size = 0; //??? + } else { + *file_size = (uint64_t)st.st_size; + } + } + + if (can_write) { + *can_write = access(path.c_str(), W_OK) == 0; + } + + return true; } bool PathIsFolderOnDisk(const std::string &path) { - return IsThingOnDisk(path, S_IFDIR); + struct stat st; + if (stat(path.c_str(), &st) == -1) { + return false; + } + + if ((st.st_mode & S_IFDIR) != S_IFDIR) { + return false; + } + + return true; } ////////////////////////////////////////////////////////////////////////// diff --git a/src/shared/h/shared/path.h b/src/shared/h/shared/path.h index 445e236c..d24f49e8 100644 --- a/src/shared/h/shared/path.h +++ b/src/shared/h/shared/path.h @@ -87,6 +87,7 @@ bool PathGlob(const std::string &folder, std::function Date: Wed, 30 Oct 2024 00:21:40 +0000 Subject: [PATCH 09/31] Move DirectDiscImage into beeb_lib. See #379. --- src/b2/BeebWindow.cpp | 2 +- src/b2/CMakeLists.txt | 2 -- src/b2/HTTPMethodsHandler.cpp | 2 +- src/b2/MemoryDiscImage.cpp | 1 + src/b2/MemoryDiscImage.h | 2 +- src/b2/b2.cpp | 2 +- src/b2/discs.cpp | 2 +- src/beeb/CMakeLists.txt | 2 ++ .../include/beeb}/DirectDiscImage.h | 8 +++---- src/{b2 => beeb/include/beeb}/DiscGeometry.h | 0 src/{b2 => beeb/src}/DirectDiscImage.cpp | 24 ++++++++----------- src/{b2 => beeb/src}/DiscGeometry.cpp | 5 ++-- 12 files changed, 24 insertions(+), 28 deletions(-) rename src/{b2 => beeb/include/beeb}/DirectDiscImage.h (90%) rename src/{b2 => beeb/include/beeb}/DiscGeometry.h (100%) rename src/{b2 => beeb/src}/DirectDiscImage.cpp (94%) rename src/{b2 => beeb/src}/DiscGeometry.cpp (99%) diff --git a/src/b2/BeebWindow.cpp b/src/b2/BeebWindow.cpp index 6f00143d..877ce9f7 100644 --- a/src/b2/BeebWindow.cpp +++ b/src/b2/BeebWindow.cpp @@ -34,7 +34,7 @@ #include "DearImguiTestUI.h" #include "debugger.h" #include "HTTPServer.h" -#include "DirectDiscImage.h" +#include #include "SavedStatesUI.h" #include "BeebLinkUI.h" #include "SettingsUI.h" diff --git a/src/b2/CMakeLists.txt b/src/b2/CMakeLists.txt index 8e49969b..bf96e6f9 100644 --- a/src/b2/CMakeLists.txt +++ b/src/b2/CMakeLists.txt @@ -16,8 +16,6 @@ add_executable(b2 MACOSX_BUNDLE ConfigsUI.cpp ConfigsUI.h ConfigsUI_private.inl DataRateUI.cpp DataRateUI.h DearImguiTestUI.cpp DearImguiTestUI.h - DirectDiscImage.cpp DirectDiscImage.h - DiscGeometry.cpp DiscGeometry.h GenerateThumbnailJob.cpp GenerateThumbnailJob.h HTTPMethodsHandler.cpp HTTPMethodsHandler.h HTTPServer.cpp HTTPServer.h diff --git a/src/b2/HTTPMethodsHandler.cpp b/src/b2/HTTPMethodsHandler.cpp index 2e59da6b..7f0f7343 100644 --- a/src/b2/HTTPMethodsHandler.cpp +++ b/src/b2/HTTPMethodsHandler.cpp @@ -16,7 +16,7 @@ #include "MemoryDiscImage.h" #include "Messages.h" #include -#include "DiscGeometry.h" +#include ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// diff --git a/src/b2/MemoryDiscImage.cpp b/src/b2/MemoryDiscImage.cpp index 5d25ccfa..9aff5c4c 100644 --- a/src/b2/MemoryDiscImage.cpp +++ b/src/b2/MemoryDiscImage.cpp @@ -13,6 +13,7 @@ #include "native_ui.h" #include #include +#include #ifdef __GNUC__ #pragma GCC diagnostic push diff --git a/src/b2/MemoryDiscImage.h b/src/b2/MemoryDiscImage.h index c132592f..08296757 100644 --- a/src/b2/MemoryDiscImage.h +++ b/src/b2/MemoryDiscImage.h @@ -6,9 +6,9 @@ #include #include -#include "DiscGeometry.h" struct LogSet; +struct DiscGeometry; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// diff --git a/src/b2/b2.cpp b/src/b2/b2.cpp index 6d7ff3f7..16e8035d 100644 --- a/src/b2/b2.cpp +++ b/src/b2/b2.cpp @@ -37,7 +37,7 @@ #include "HTTPServer.h" #include "HTTPMethodsHandler.h" #include -#include "DirectDiscImage.h" +#include #include "discs.h" #if SYSTEM_OSX #include diff --git a/src/b2/discs.cpp b/src/b2/discs.cpp index ff13ce4f..d49662dc 100644 --- a/src/b2/discs.cpp +++ b/src/b2/discs.cpp @@ -1,7 +1,7 @@ #include #include "discs.h" #include "load_save.h" -#include "DiscGeometry.h" +#include ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// diff --git a/src/beeb/CMakeLists.txt b/src/beeb/CMakeLists.txt index 3cfc652f..828b15db 100644 --- a/src/beeb/CMakeLists.txt +++ b/src/beeb/CMakeLists.txt @@ -33,6 +33,8 @@ set(SRCS ${S}/PCD8572.cpp ${I}/PCD8572.h ${I}/PCD8572.inl ${S}/uef.cpp ${I}/uef.h ${S}/BBCMicroState.cpp ${I}/BBCMicroState.h ${I}/BBCMicroState.inl + ${S}/DiscGeometry.cpp ${I}/DiscGeometry.h + ${S}/DirectDiscImage.cpp ${I}/DirectDiscImage.h ) if(MSVC) diff --git a/src/b2/DirectDiscImage.h b/src/beeb/include/beeb/DirectDiscImage.h similarity index 90% rename from src/b2/DirectDiscImage.h rename to src/beeb/include/beeb/DirectDiscImage.h index 1948a76c..9cf17206 100644 --- a/src/b2/DirectDiscImage.h +++ b/src/beeb/include/beeb/DirectDiscImage.h @@ -6,10 +6,10 @@ // // Write-through disc image. // -// Does an fopen, fseek, fgetc/fputc, fclose for every byte. Needs more -// support from the disc system for better performance - should ideally -// open the file when the motor spins up, and close it when it spins down. -// It's then pretty obvious when the file is safe to overwrite. +// Opens disk image file as required, then closes on motor spin down. This +// strikes a decent balance between performance (fopen per byte is pretty +// terrible on Windows...) and making it easy to rewrite a DFS disk image that's +// in use (file can be overwritten when motor is off). // ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// diff --git a/src/b2/DiscGeometry.h b/src/beeb/include/beeb/DiscGeometry.h similarity index 100% rename from src/b2/DiscGeometry.h rename to src/beeb/include/beeb/DiscGeometry.h diff --git a/src/b2/DirectDiscImage.cpp b/src/beeb/src/DirectDiscImage.cpp similarity index 94% rename from src/b2/DirectDiscImage.cpp rename to src/beeb/src/DirectDiscImage.cpp index 77ee1fec..97608a01 100644 --- a/src/b2/DirectDiscImage.cpp +++ b/src/beeb/src/DirectDiscImage.cpp @@ -1,18 +1,14 @@ #include #include -#include "misc.h" -#include "DirectDiscImage.h" -#include "native_ui.h" -#include "Messages.h" +#include #include #include #include +#include ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -LOG_EXTERN(OUTPUT); - const std::string DirectDiscImage::LOAD_METHOD_DIRECT = "direct"; ////////////////////////////////////////////////////////////////////////// @@ -89,11 +85,14 @@ std::string DirectDiscImage::GetLoadMethod() const { // TODO - basically the same as MemoryDiscImage. std::string DirectDiscImage::GetDescription() const { - return strprintf("%s %s %zuT x %zuS", - m_geometry.double_sided ? "DS" : "SS", - m_geometry.double_density ? "DD" : "SD", - m_geometry.num_tracks, - m_geometry.sectors_per_track); + char description[100]; + snprintf(description, sizeof description, "%s %s %zuT x %zuS", + m_geometry.double_sided ? "DS" : "SS", + m_geometry.double_density ? "DD" : "SD", + m_geometry.num_tracks, + m_geometry.sectors_per_track); + + return description; } ////////////////////////////////////////////////////////////////////////// @@ -249,8 +248,6 @@ bool DirectDiscImage::fopenAndSeek(bool write, mode = "rb"; } - LOGF(OUTPUT, "Opening: %s (mode=%s)\n", m_path.c_str(), mode); - m_fp = fopenUTF8(m_path.c_str(), mode); if (!m_fp) { return false; @@ -272,7 +269,6 @@ bool DirectDiscImage::fopenAndSeek(bool write, void DirectDiscImage::Close() const { if (m_fp) { - LOGF(OUTPUT, "Closing: %s\n", m_path.c_str()); fclose(m_fp); m_fp = nullptr; } diff --git a/src/b2/DiscGeometry.cpp b/src/beeb/src/DiscGeometry.cpp similarity index 99% rename from src/b2/DiscGeometry.cpp rename to src/beeb/src/DiscGeometry.cpp index d84289b4..db6809b2 100644 --- a/src/b2/DiscGeometry.cpp +++ b/src/beeb/src/DiscGeometry.cpp @@ -1,11 +1,10 @@ #include #include -#include "Messages.h" -#include "DiscGeometry.h" +#include #include -#include "native_ui.h" #include #include +#include ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// From b1b0a7ee6f5c9509331bc84042531b56a49981a8 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Thu, 31 Oct 2024 00:54:35 +0000 Subject: [PATCH 10/31] Move MemoryDiscImage into beeb_lib. Still a bit WIP. --- src/b2/BeebWindow.cpp | 8 +- src/b2/CMakeLists.txt | 2 +- src/b2/HTTPMethodsHandler.cpp | 2 +- src/b2/LoadMemoryDiscImage.cpp | 155 +++++++++++++++ src/b2/LoadMemoryDiscImage.h | 18 ++ src/b2/b2.cpp | 5 +- src/beeb/CMakeLists.txt | 1 + .../include/beeb}/MemoryDiscImage.h | 3 +- src/{b2 => beeb/src}/MemoryDiscImage.cpp | 181 ++---------------- 9 files changed, 196 insertions(+), 179 deletions(-) create mode 100644 src/b2/LoadMemoryDiscImage.cpp create mode 100644 src/b2/LoadMemoryDiscImage.h rename src/{b2 => beeb/include/beeb}/MemoryDiscImage.h (95%) rename src/{b2 => beeb/src}/MemoryDiscImage.cpp (68%) diff --git a/src/b2/BeebWindow.cpp b/src/b2/BeebWindow.cpp index 877ce9f7..4540d4e3 100644 --- a/src/b2/BeebWindow.cpp +++ b/src/b2/BeebWindow.cpp @@ -11,7 +11,8 @@ #include "keymap.h" #include "keys.h" #include -#include "MemoryDiscImage.h" +#include +#include "LoadMemoryDiscImage.h" #include "BeebWindows.h" #include #include @@ -1935,8 +1936,7 @@ void BeebWindow::DoDiscImageSubMenu(int drive, bool boot) { *file_item.new_disc_type->geometry, m_msg); } else { - new_disc_image = MemoryDiscImage::LoadFromFile(file_item.path, - m_msg); + new_disc_image = LoadMemoryDiscImage(file_item.path, m_msg); } this->DoDiscImageSubMenuItem(drive, std::move(new_disc_image), @@ -3271,7 +3271,7 @@ const std::string &BeebWindow::GetConfigName() const { ////////////////////////////////////////////////////////////////////////// void BeebWindow::Launch(const BeebWindowLaunchArguments &arguments) { - std::shared_ptr disc_image = MemoryDiscImage::LoadFromFile(arguments.file_path, m_msg); + std::shared_ptr disc_image = LoadMemoryDiscImage(arguments.file_path, m_msg); if (!disc_image) { return; } diff --git a/src/b2/CMakeLists.txt b/src/b2/CMakeLists.txt index bf96e6f9..694751b3 100644 --- a/src/b2/CMakeLists.txt +++ b/src/b2/CMakeLists.txt @@ -21,7 +21,6 @@ add_executable(b2 MACOSX_BUNDLE HTTPServer.cpp HTTPServer.h JobQueue.cpp JobQueue.h KeymapsUI.cpp KeymapsUI.h - MemoryDiscImage.cpp MemoryDiscImage.h MessageQueue.cpp MessageQueue.h Messages.cpp Messages.h MessagesUI.cpp MessagesUI.h @@ -50,6 +49,7 @@ add_executable(b2 MACOSX_BUNDLE roms.cpp roms.h profiler.cpp profiler.h joysticks.cpp joysticks.h + LoadMemoryDiscImage.cpp LoadMemoryDiscImage.h ) if(OSX) diff --git a/src/b2/HTTPMethodsHandler.cpp b/src/b2/HTTPMethodsHandler.cpp index 7f0f7343..b77cb4d1 100644 --- a/src/b2/HTTPMethodsHandler.cpp +++ b/src/b2/HTTPMethodsHandler.cpp @@ -13,7 +13,7 @@ #include "BeebWindow.h" #include "BeebThread.h" #include -#include "MemoryDiscImage.h" +#include #include "Messages.h" #include #include diff --git a/src/b2/LoadMemoryDiscImage.cpp b/src/b2/LoadMemoryDiscImage.cpp new file mode 100644 index 00000000..9e5425a9 --- /dev/null +++ b/src/b2/LoadMemoryDiscImage.cpp @@ -0,0 +1,155 @@ +#include +#include "LoadMemoryDiscImage.h" +#include +#include +#include +#include +#include + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#elif defined _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4100) //C4100: 'IDENTIFIER': unreferenced formal parameter +#pragma warning(disable : 4127) //C4127: conditional expression is constant +#pragma warning(disable : 4244) //C4244: 'THING': conversion from 'TYPE' to 'TYPE', possible loss of data +#pragma warning(disable : 4334) //C4334: 'SHIFT': result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?) +#endif +#include +#include +#include +#include +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#elif defined _MSC_VER +#pragma warning(pop) +#endif + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +static const std::string LOAD_METHOD_ZIP = "zip"; + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +static const mz_uint BAD_INDEX = ~(mz_uint)0; + +static bool LoadDiscImageFromZipFile( + std::string *image_name, + std::vector *data, + DiscGeometry *geometry, + const std::string &zip_file_name, + const LogSet &logs) { + mz_zip_archive za; + bool za_opened = 0; + bool good = 0; + mz_uint image_index = BAD_INDEX; + mz_zip_archive_file_stat image_stat = {}; + DiscGeometry image_geometry; + // void *data=NULL; + // + memset(&za, 0, sizeof za); + if (!mz_zip_reader_init_file(&za, zip_file_name.c_str(), 0)) { + logs.e.f("failed to init zip file reader\n"); + goto done; + } + + za_opened = true; + + for (mz_uint i = 0; i < mz_zip_reader_get_num_files(&za); ++i) { + // the zip file name length is 16 bits or so, so there'll + // never be any mz_uint wrap or overflow. + mz_uint name_size = mz_zip_reader_get_filename(&za, i, NULL, 0); + + std::vector name(name_size + 1); + + mz_zip_reader_get_filename(&za, i, name.data(), (mz_uint)name.size()); + + mz_zip_archive_file_stat stat; + if (!mz_zip_reader_file_stat(&za, i, &stat)) { + logs.e.f("failed to get file info from zip file: %s\n", zip_file_name.c_str()); + logs.i.f("(problem file: %s)\n", name.data()); + goto done; + } + + if (stat.m_uncomp_size > SIZE_MAX) { + logs.e.f("file is too large in zip file: %s\n", zip_file_name.c_str()); + logs.i.f("(problem file: %s)\n", name.data()); + goto done; + } + + DiscGeometry g; + if (FindDiscGeometryFromFileDetails(&g, name.data(), stat.m_uncomp_size, nullptr)) { + if (image_index != BAD_INDEX) { + logs.e.f("zip file contains multiple disc images: %s\n", zip_file_name.c_str()); + logs.i.f("(at least: %s, %s)\n", name.data(), image_name->c_str()); + goto done; + } + + *image_name = name.data(); + image_index = i; + image_geometry = g; + image_stat = stat; + } + } + + if (image_index == BAD_INDEX) { + logs.e.f("zip file contains no disc images: %s\n", zip_file_name.c_str()); + goto done; + } + + data->resize((size_t)image_stat.m_uncomp_size); + if (!mz_zip_reader_extract_to_mem(&za, image_index, data->data(), data->size(), 0)) { + logs.e.f("failed to extract disc image from zip: %s\n", zip_file_name.c_str()); + logs.i.f("(disc image: %s)\n", image_name->c_str()); + goto done; + } + + good = true; + *geometry = image_geometry; + +done:; + if (za_opened) { + mz_zip_reader_end(&za); + za_opened = 0; + } + + return good; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +std::shared_ptr LoadMemoryDiscImage(std::string path, const LogSet &logs) { + std::vector data; + DiscGeometry geometry; + std::string method; + + if (PathCompare(PathGetExtension(path), ".zip") == 0) { + std::string name; + if (!LoadDiscImageFromZipFile(&name, &data, &geometry, path, logs)) { + return nullptr; + } + + method = LOAD_METHOD_ZIP; + + // Just some fairly arbitrary separator that's easy to find + // later and rather unlikely to appear in a file name. + path += "::" + name; + } else { + if (!LoadFile(&data, path, &logs)) { + return nullptr; + } + + if (!FindDiscGeometryFromFileDetails(&geometry, path.c_str(), data.size(), &logs)) { + return nullptr; + } + + method = MemoryDiscImage::LOAD_METHOD_FILE; + } + + return MemoryDiscImage::LoadFromBuffer(path, method, data.data(), data.size(), geometry, logs); +} diff --git a/src/b2/LoadMemoryDiscImage.h b/src/b2/LoadMemoryDiscImage.h new file mode 100644 index 00000000..da5dde8b --- /dev/null +++ b/src/b2/LoadMemoryDiscImage.h @@ -0,0 +1,18 @@ +#ifndef HEADER_05B49D106E2F4A6D87963A4287662CF8 // -*- mode:c++ -*- +#define HEADER_05B49D106E2F4A6D87963A4287662CF8 + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +#include +#include + +class MemoryDiscImage; +struct LogSet; + +std::shared_ptr LoadMemoryDiscImage(std::string path, const LogSet &logs); + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/src/b2/b2.cpp b/src/b2/b2.cpp index 16e8035d..d277cac6 100644 --- a/src/b2/b2.cpp +++ b/src/b2/b2.cpp @@ -13,7 +13,8 @@ #include #include #include -#include "MemoryDiscImage.h" +#include "LoadMemoryDiscImage.h" +#include #include #include "VBlankMonitor.h" #include @@ -1375,7 +1376,7 @@ static bool main2(int argc, char *argv[], const std::shared_ptr &in if (options.direct_disc[i]) { ia.init_disc_images[i] = DirectDiscImage::CreateForFile(options.discs[i], init_messages); } else { - ia.init_disc_images[i] = MemoryDiscImage::LoadFromFile(options.discs[i], init_messages); + ia.init_disc_images[i] = LoadMemoryDiscImage(options.discs[i], init_messages); } if (!ia.init_disc_images[i]) { diff --git a/src/beeb/CMakeLists.txt b/src/beeb/CMakeLists.txt index 828b15db..2f84fe01 100644 --- a/src/beeb/CMakeLists.txt +++ b/src/beeb/CMakeLists.txt @@ -35,6 +35,7 @@ set(SRCS ${S}/BBCMicroState.cpp ${I}/BBCMicroState.h ${I}/BBCMicroState.inl ${S}/DiscGeometry.cpp ${I}/DiscGeometry.h ${S}/DirectDiscImage.cpp ${I}/DirectDiscImage.h + ${S}/MemoryDiscImage.cpp ${I}/MemoryDiscImage.h ) if(MSVC) diff --git a/src/b2/MemoryDiscImage.h b/src/beeb/include/beeb/MemoryDiscImage.h similarity index 95% rename from src/b2/MemoryDiscImage.h rename to src/beeb/include/beeb/MemoryDiscImage.h index 08296757..726ddae8 100644 --- a/src/b2/MemoryDiscImage.h +++ b/src/beeb/include/beeb/MemoryDiscImage.h @@ -16,7 +16,6 @@ struct DiscGeometry; class MemoryDiscImage : public DiscImage { public: static const std::string LOAD_METHOD_FILE; - static const std::string LOAD_METHOD_ZIP; static const uint8_t FILL_BYTE; @@ -24,7 +23,7 @@ class MemoryDiscImage : public DiscImage { // If the load succeeds, the method will be LOAD_METHOD_FILE or // LOAD_METHOD_ZIP. - static std::shared_ptr LoadFromFile(std::string path, const LogSet &logs); + //static std::shared_ptr LoadFromFile(std::string path, const LogSet &logs); ~MemoryDiscImage(); diff --git a/src/b2/MemoryDiscImage.cpp b/src/beeb/src/MemoryDiscImage.cpp similarity index 68% rename from src/b2/MemoryDiscImage.cpp rename to src/beeb/src/MemoryDiscImage.cpp index 9aff5c4c..55163546 100644 --- a/src/b2/MemoryDiscImage.cpp +++ b/src/beeb/src/MemoryDiscImage.cpp @@ -1,48 +1,26 @@ #include #include #include -#include "MemoryDiscImage.h" +#include #include #include -#include "misc.h" #include #include #include -#include "load_save.h" #include -#include "native_ui.h" #include #include #include -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsign-conversion" -#pragma GCC diagnostic ignored "-Wunused-parameter" -#elif defined _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4100) //C4100: 'IDENTIFIER': unreferenced formal parameter -#pragma warning(disable : 4127) //C4127: conditional expression is constant -#pragma warning(disable : 4244) //C4244: 'THING': conversion from 'TYPE' to 'TYPE', possible loss of data -#pragma warning(disable : 4334) //C4334: 'SHIFT': result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?) -#endif -#include -#include -#include -#include -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#elif defined _MSC_VER -#pragma warning(pop) -#endif - // static const size_t BYTES_PER_SECTOR=256; // static const size_t SECTORS_PER_TRACK=10; //static const size_t NUM_TRACKS=80; const uint8_t MemoryDiscImage::FILL_BYTE = 0xe5; +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + const std::string MemoryDiscImage::LOAD_METHOD_FILE = "file"; -const std::string MemoryDiscImage::LOAD_METHOD_ZIP = "zip"; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -105,144 +83,6 @@ std::shared_ptr MemoryDiscImage::LoadFromBuffer( ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -static const mz_uint BAD_INDEX = ~(mz_uint)0; - -static bool LoadDiscImageFromZipFile( - std::string *image_name, - std::vector *data, - DiscGeometry *geometry, - const std::string &zip_file_name, - const LogSet &logs) { - mz_zip_archive za; - bool za_opened = 0; - bool good = 0; - mz_uint image_index = BAD_INDEX; - mz_zip_archive_file_stat image_stat = {}; - DiscGeometry image_geometry; - // void *data=NULL; - // - memset(&za, 0, sizeof za); - if (!mz_zip_reader_init_file(&za, zip_file_name.c_str(), 0)) { - logs.e.f("failed to init zip file reader\n"); - goto done; - } - - za_opened = true; - - for (mz_uint i = 0; i < mz_zip_reader_get_num_files(&za); ++i) { - // the zip file name length is 16 bits or so, so there'll - // never be any mz_uint wrap or overflow. - mz_uint name_size = mz_zip_reader_get_filename(&za, i, NULL, 0); - - std::vector name(name_size + 1); - - mz_zip_reader_get_filename(&za, i, name.data(), (mz_uint)name.size()); - - mz_zip_archive_file_stat stat; - if (!mz_zip_reader_file_stat(&za, i, &stat)) { - logs.e.f("failed to get file info from zip file: %s\n", zip_file_name.c_str()); - logs.i.f("(problem file: %s)\n", name.data()); - goto done; - } - - if (stat.m_uncomp_size > SIZE_MAX) { - logs.e.f("file is too large in zip file: %s\n", zip_file_name.c_str()); - logs.i.f("(problem file: %s)\n", name.data()); - goto done; - } - - DiscGeometry g; - if (FindDiscGeometryFromFileDetails(&g, name.data(), stat.m_uncomp_size, nullptr)) { - if (image_index != BAD_INDEX) { - logs.e.f("zip file contains multiple disc images: %s\n", zip_file_name.c_str()); - logs.i.f("(at least: %s, %s)\n", name.data(), image_name->c_str()); - goto done; - } - - *image_name = name.data(); - image_index = i; - image_geometry = g; - image_stat = stat; - } - } - - if (image_index == BAD_INDEX) { - logs.e.f("zip file contains no disc images: %s\n", zip_file_name.c_str()); - goto done; - } - - data->resize((size_t)image_stat.m_uncomp_size); - if (!mz_zip_reader_extract_to_mem(&za, image_index, data->data(), data->size(), 0)) { - logs.e.f("failed to extract disc image from zip: %s\n", zip_file_name.c_str()); - logs.i.f("(disc image: %s)\n", image_name->c_str()); - goto done; - } - - good = true; - *geometry = image_geometry; - -done:; - if (za_opened) { - mz_zip_reader_end(&za); - za_opened = 0; - } - - return good; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -static bool LoadDiscImage(std::vector *data, - DiscGeometry *geometry, - const std::string &path, - const LogSet &logs) { - if (!LoadFile(data, path, &logs)) { - return false; - } - - if (!FindDiscGeometryFromFileDetails(geometry, path.c_str(), data->size(), &logs)) { - return false; - } - - return true; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -std::shared_ptr MemoryDiscImage::LoadFromFile( - std::string path, - const LogSet &logs) { - std::vector data; - DiscGeometry geometry; - std::string method; - - if (PathCompare(PathGetExtension(path), ".zip") == 0) { - std::string name; - if (!LoadDiscImageFromZipFile(&name, &data, &geometry, path, logs)) { - return nullptr; - } - - method = LOAD_METHOD_ZIP; - - // Just some fairly arbitrary separator that's easy to find - // later and rather unlikely to appear in a file name. - path += "::" + name; - } else { - if (!LoadDiscImage(&data, &geometry, path, logs)) { - return nullptr; - } - - method = LOAD_METHOD_FILE; - } - - return LoadFromBuffer(path, method, data.data(), data.size(), geometry, logs); -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - MemoryDiscImage::MemoryDiscImage() : m_data(new Data) { } @@ -344,11 +184,14 @@ std::string MemoryDiscImage::GetLoadMethod() const { ////////////////////////////////////////////////////////////////////////// std::string MemoryDiscImage::GetDescription() const { - return strprintf("%s %s %zuT x %zuS", - m_data->geometry.double_sided ? "DS" : "SS", - m_data->geometry.double_density ? "DD" : "SD", - m_data->geometry.num_tracks, - m_data->geometry.sectors_per_track); + char description[100]; + snprintf(description, sizeof description, "%s %s %zuT x %zuS", + m_data->geometry.double_sided ? "DS" : "SS", + m_data->geometry.double_density ? "DD" : "SD", + m_data->geometry.num_tracks, + m_data->geometry.sectors_per_track); + + return description; } ////////////////////////////////////////////////////////////////////////// From 5f11deb869d487affc142522c07ca478c04ac09d Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Thu, 31 Oct 2024 22:54:37 +0000 Subject: [PATCH 11/31] Update .gitignore. * Remove a bunch of redundant junk * Don't ignore *.etl after all. They can go in the build folder * Can't remember what .dir-locals-build.sh is/was, but the only reference Google finds is to the b2 repo, so it can probably go Fix #377. --- .gitignore | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.gitignore b/.gitignore index 6e7b4e44..7494574c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,4 @@ /.dir-locals.el -/0Rel*/ -/games/ -beeb_dis.txt -imgui.ini /build/ -/cmake-build-*/ -/.dir-locals-build.sh /etc/b2_tests/[Zz]/ /dependencies/ProcessorTests/ -*.etl From 8c1970e9b58275ca211ed353d05d06d7ac88aeb0 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Thu, 31 Oct 2024 23:08:49 +0000 Subject: [PATCH 12/31] Add 3.5" disk drive samples. See #2, which _still_ applies! Fix #380. --- etc/samples_floppy/35_seek_12ms.wav | Bin 0 -> 26540 bytes etc/samples_floppy/35_seek_20ms.wav | Bin 0 -> 44144 bytes etc/samples_floppy/35_seek_2ms.wav | Bin 0 -> 7450 bytes etc/samples_floppy/35_seek_6ms.wav | Bin 0 -> 22230 bytes etc/samples_floppy/35_spin_empty.wav | Bin 0 -> 17708 bytes etc/samples_floppy/35_spin_end.wav | Bin 0 -> 17684 bytes etc/samples_floppy/35_spin_loaded.wav | Bin 0 -> 17708 bytes etc/samples_floppy/35_spin_start_empty.wav | Bin 0 -> 17708 bytes etc/samples_floppy/35_spin_start_loaded.wav | Bin 0 -> 35308 bytes etc/samples_floppy/35_step_1_1.wav | Bin 0 -> 6878 bytes etc/samples_floppy/README.md | 7 ++++++- src/b2/b2.cpp | 11 +++++++++++ 12 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 etc/samples_floppy/35_seek_12ms.wav create mode 100644 etc/samples_floppy/35_seek_20ms.wav create mode 100644 etc/samples_floppy/35_seek_2ms.wav create mode 100644 etc/samples_floppy/35_seek_6ms.wav create mode 100644 etc/samples_floppy/35_spin_empty.wav create mode 100644 etc/samples_floppy/35_spin_end.wav create mode 100644 etc/samples_floppy/35_spin_loaded.wav create mode 100644 etc/samples_floppy/35_spin_start_empty.wav create mode 100644 etc/samples_floppy/35_spin_start_loaded.wav create mode 100644 etc/samples_floppy/35_step_1_1.wav diff --git a/etc/samples_floppy/35_seek_12ms.wav b/etc/samples_floppy/35_seek_12ms.wav new file mode 100644 index 0000000000000000000000000000000000000000..8009176ba96131a6acdb7c0b71481cd2023f36b6 GIT binary patch literal 26540 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 literal 0 HcmV?d00001 diff --git a/etc/samples_floppy/35_seek_20ms.wav b/etc/samples_floppy/35_seek_20ms.wav new file mode 100644 index 0000000000000000000000000000000000000000..42030b3a357e6118d93ab6fae04e5533a59359b1 GIT binary patch literal 44144 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 literal 0 HcmV?d00001 diff --git a/etc/samples_floppy/35_spin_empty.wav b/etc/samples_floppy/35_spin_empty.wav new file mode 100644 index 0000000000000000000000000000000000000000..5b1006f3fa2aece6360fe1345e6ddd60c8fd5971 GIT binary patch literal 17708 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 literal 0 HcmV?d00001 diff --git a/etc/samples_floppy/35_spin_end.wav b/etc/samples_floppy/35_spin_end.wav new file mode 100644 index 0000000000000000000000000000000000000000..b29747517eb53b4b21e08459ba27e1762fd4edab GIT binary patch literal 17684 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

J-D+4gI=c=-%^HH*EUBC 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 literal 0 HcmV?d00001 diff --git a/etc/samples_floppy/35_spin_start_empty.wav b/etc/samples_floppy/35_spin_start_empty.wav new file mode 100644 index 0000000000000000000000000000000000000000..f420413b780da83dece0e763ad6e2bbb406c6fab GIT binary patch literal 17708 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 literal 0 HcmV?d00001 diff --git a/etc/samples_floppy/35_spin_start_loaded.wav b/etc/samples_floppy/35_spin_start_loaded.wav new file mode 100644 index 0000000000000000000000000000000000000000..18d5d607e207bb90a04751af43d50cd860b33613 GIT binary patch literal 35308 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�Vmw_1K#S$_;XXkCk}5uovH%o0y~#7N;{3(;GeQ zO)lXFbh{i4u{|0c6|#2l0JwK6@yh~qU=ZlA3)%N($Wv8fY8gj9@O5fp zhs0#~)9`;5MkcH3vXC(-%xEJSa})ek7rI&pfpq@jO8;`r-ORyNX7~Z?o!)|=qsh;P zs6%+*T?*_*VdDNs)(VvZ;a4VOD{HH&vA!=76j>fBC35=#irb<4RiQbI+^(Al(WFij z1c|#i!qH)i$mt1lah8!Be!mIOSOHN`{9>C`p*v1!7 zrE*nSca|QS((*cyk{EO^D{~{U5oMvG9YQ#gL#hc3M`Jg!72N zGo&B~k&B#DQa;UyRAoetij(i|iVjU=981WEZGkIiK_{QczImXz61*4tKN>&w9~F$X zJWdgRy+q2RS?`b-eO2*HX^3QUlKIGrpDxTQm;8J#+TceQUZKg4(4zfB*|H*_1(KGT ztX3lO%kPkkeayggP|HB1xdxPr1_+&LIa0A!AQo+oL5riXHnMU+XGED9i;rAmaz>Gv zQB))P>CcSJVHP(qhKqQZcSMg05qK_eTn)#Q^+0;YkV{;`IChdFJPA$5IL@$AKyoNM zc`rJ@hf%KLtTEWI9?;v0-xon|WCi(mu=xdMdp-R68>tvZZMPR%Sebr((cJ7%ZCZ)R zq$Q&^?X`7gAtV2X69dc2SJB^M;J(`6mLlO_8OV6SC6}M;h zSdjTh#a1l%5iH^*Z21{pUEnwivbcdye!=k^ANPoNw-}YszYedr1Ft<3Df*pCMt36H zT1a3qbU!ziAPF(GfdoAzrvLw3%3N&X9P*(viT=hgBZD2;A40CI50cdpyxM|_ZBOby z{h?c=Z4i-SA3pB^0vX_(KM6@(4z^l`G_AujY$r>z3d6)V%HhT$VfGc^CjOzG=)?oTR*v_Buva-9v6tra$G0IGA=wHyb z04w?%akS`hPv)XA7PK(G&*`l0`3hYh;OJp&&`Ky7k45hcuNr|V8dB@5#jEOIo4RCh zD&TVqJI|4P(ukwIld~U#-I~luRxulgK@xAN1VpnUDU7jaMG7L3(3aeL6zhHFL6NLx zS%fuR;p7Qd^T|Tq&*1n2Jsr*{+9C(-n1{N^dMQSm0$=+PojHYP-^6`HLx08Vw1LtO8Zw?bJ-lgL5k?IN!vsA9#;GZO5xa8DdQc`~jBSUY2>pl@5KcM&h zJh{1z4pJV2oVRv#Ge6gkz-~$=Pj)HU<#^a>aC;1vqc7ZUhV8Bknu(+?lA9`XemqxE z>}V05OG39CwV9E+@Vh(Q{sXT&14&+rB=3Uj2N=mou6-X|@f|Coa=+AQVSXgA2zEkN zY}EoI)Iu`rqF-{z9x1hvhT4urkbN2|amc#!qMVodHWbL_QLu9S~8?N)> ze~(Ytmnhb-%Dp1+i>bMDTD)XVs3-t#%mNqlGNQ6bZzR@FkXlv7Qi}6fFjz;3& ze&hL%{~aUwd^*}O7yq>YU$_!&+m7cFy*|$jOGVC4KH38U6Q7aap}7w{dAxC19 z)A2eRa#fHK7FSNhC==E{7Ka-(nCG_4Sx30l0ngC~%l8X& zIhy&N#AA$eOad7X!YcO0!iz`h#QB1tMTQg5a52Y2ri24N#w@;A#oq)R?!1Q**}vfh ztNY&ZO3;Sr-zzvP?`1W395d=+{POVO|0PyP>|oO{<|P8&rD3LX!mae&DLZnS!{KaF zxNV`ag7&|oH!;*J1pVJz0kP(lRo^Kq+`6}?6xCF zDJ?Rmvmb?wpug>#W`3Bd68{z{5DmSHCfr3IF7ddF?%v_J zh16Z)y&z!O@k;jOc!Okqar9Z_(9cz(ncY}4Mnx87r=V2aN3yM1oS7^F?+WuO5-KZV zaq7ThLAq_QI&FB>72Rm-yl#h9{eW&Yxhh3 zF&nG-RxsyQNB*~BF%B@t7qKUg;lT%V)c|XUb9d1{ITG_a8IqP7S<3*GS&*BY@E|u- z7s6-BEaZU`BJJWS(=&z~&b(w{j*|0PB0RPZmKR(45}kYm_b)ONCwM;K(6PrE$7V;@i*C^mFLz7}Jvklz zHU;yw!GHDU=mxg$3IZI+;}_!nq2Ros__dMPHCd@X2JbeNe82=gAB`0ph>h#UoYg~% zB+@C5jOJ%tiLvf+jOV`NpD!TOd$1&{pn0yt{b}IBDd@pi5cDYYq&M2qnVftRuxdjh zx5lioZjRox;nQ~Dt{*u=;<_Gal;n{9ptoQScIZE5b~o2PhZVa5-yS2U;&0-y;$B9Y z6VB&$xN3 z{FdKUcko3KaIEB9+xe_2 zloh~=%K1gn>`ZW1{Epy`5HlIa;{!JG8TWe%wX(;|6$e?IgliJ-?gfABht5M#y9|Uj z5AOcWu>|gJ{@)pbMr4ny9h`g2v9u4FJBixk96O_8Y0`4vtWc4Uqa@UdH7)FDn%K!k z$U!Rytu=(3Wx=F{I7^UnK1M9KFFjOChA$QNGYO+e!Zmdsf_7vNK|dB;5Ph_Rg1&Kg zL35Ar-IwtQr=ji$(VWDKhoSYTBjbWmFCZ5p=YqZ;!2{W;>N&p+GJCO(N06w}=XmJk zNK7&;LS~M%aJ4wRua5l4{y35W=>Y#5@~#0RX^KqAzGJd4S!YK}TRU3O4ta0I?9~3B zZzzq_%f4G8MUr2w%4`;LW;YY}6kq4U*9kRm7~3nygNY=Ir#lPXCwSh;h!W;%57ab{ug5=NBKJ6h*hSN%^lBY^7&{qxHq0!BD7Ncz8HMY$y^F%ccHPd%>6^|EU}|x z&(A}ppv7g5RgsL(9Bj=*eAHO1%@{H#gR$hjuE@ycts zf}qL`=urYf5*@jNtbIc}H16!dGDq+n&K#s*tb!QDvW7vK;GN`-)ns09aU|fI1f(70dj);>GA|adEbb{ilPcE{kFDXe-NfADXHs$h z)Z8r{+|KMEF2QSp#B=jr_EO0RJwl<(NJ6$fJ+qRGcVf5YU6b=;0H7{T})S=Y2&BL@(u<5_4E^PxhU% zumJMUi~NeED2a3wM{0{BpCyrJk<#4AtRNWK2QC}$q@pNxBr%k`;Dm*z{l?=pcld~o zedKt}9fiXmkccP9<^_lDGtes;h>K{EWbGwZlxX)Mzk2AzJTIIpNKW}3vN5y}4v7`q8*w4Rf*XpDuCOoilRs`9Pa9m&B|=Kpd$%Wuj#exp2= zxsby-nS0rTP-Zw4G)i730M|dEpHfwN!7NF<_nP+~nc*++N+Q1(eD<0DzdL?V=82u| zl+V2X>Tp+N^($O>j~|lgDuy!z1IueU`zy2Y9sY=JB-}9}qnRK*x6n+%D`LNoVM7G1 z$X=7H!HQz#B}=;x4L%0~7VCHvJ9&ol?m8&o7Uy3^2G3&+UqY#5LqyMn?-C`YLrRLm zlNwlwMoxC2Ew;S#|Ln{-(17GWhJq}ACW`I{n(2;R8OA5WIDP}^48t1z#QE*;04+Hh z!tW|vEeD5q-$dw}Xrdo}y>e{1pnZv1cR|0zp~tZmhxkOYLE?pvU@s5z>AC-nZVzKP z%&4v-gA)HINbUz_K;oMic;-eQ#N*07g$1~GL0-wur!vo#u%EKKXa(lG6h|b~RdOEH z__Q=sN*+8LdLofeKHld+3X*b2loZ7bzGa>yTOzp%$uXaV{&Vp2p5t@xLW5K)??ByM zIQ$x$#D*kDzr~y?*jlkWVrSC8anai>NS9>)lEHte+m&+UvI!oswj;F-dDRTdEt#D* z96#Vi>+>wgp#k2u2|R9%WY_0+vYVwmq;e;_Tgpz$RnZI?eQ~awkFkjkrQ=>H(5-OC z$AqJwi5=~e_)j!W{6cCz^KqVNS}v@Hat)i@y2W0h;?X=%r9hk4;b;=LnBK9bLbV4;7Hb>}@4hik*}GaI;}?woHOCVU zL9thm@3YAD8J-0vp5ghlV><-NOQe*rJM1p>R4P1MdESSP9>V4vcP!K;&XZqX$7kHf z{>c8aFR+Z_qs7umo?c-_gM1RtCz44Mycgh|U;-T(O6kZ#4(JyjoD<2(hup}nr`b3} zP9&ow8Ish^ zJc|@2;l0#EM2cl6X*n~Y%Ad-)vgYJ43^*y=e1PmcDg$DCc|eJJGy*$Fooo%b9{H1SciTzU)3L`BRyZ5Z}abhy?oiUnI{37m~v{iSGnQ zyzEc> z8ve_(j96^2cvQ{NLCFNBgLaV{@p_W)66p~OClO{2#vzeoPCgMdDmX*3m8sD5aI{Bo zqhL$HcVC>je$3np7CMGaIqdi+@mi#K*~=faUNJWqB|C_MhQSj58O~!6|j;hj8vG`uYaDC~?6J$GcsJ z<~z(#0^O3^{s5hy`RzA;{Q+LQ;+14qO=Ma6=LNwDSF<~qsstXW9O$AdKDa8lOtOjX zpuC@R3<7EN1UYo#(-w?G(3<2I#7~!D46+w1Wf=LU{=_NLbJCqSWhh++vX*olk^CaEYiqDro0%uUQIhX@%6!UL#Ac*KrZaQ5 zNbW8iuM1^@b%t>KN^CWis^#C*h1SsbAl1vgR3o?WZaKTtE}+V~ggWb7A~A``Ch(iS zL}1N{m1SJ@@y)U)b#5d~u!SJYZ;nPvR!TBImq0>0!9*LerxJ&4=xytv7{pXbV=S;w*Lo55m|gI!0id;?TTU4K0p&DBnYeHD3;spK;!lbM=BMsPAY zDap>xgVR!1U+-weX(!Kf4_YP86>lAnrSapx1ocY=Qlwn+t(Ey-G6d3vArWsy&`>#K zCSl)9spd%Kt0C{DE-Bbea?K_9zbsl(9E_0(4Nl0GBzLeyQn(;lP3g1{+bdp2BTkFr zwPXdpK#gE*$>Se}+fobJ#5fPa^9%6$0^Cl>Oz*j(eKVm(BQ zzMvUi?k1jCEURSLQ$m6Cv_YrHou}CQ+kCbQ+U4(htb*Q^{J)-&U1H4f91?jOTuthc;+Kj;QFW|fU-Bda z>3JAVwJ+hXZOCrhZ9vvlc*r@;u&CWQuPqW)1#45E_mWeW%y>ygCs%$3wj}%%9kkfmqFE4)s-?GurS@It}VO_Eh$B zZ_Ot)&=}bRU+OsW_aIC-^acLPUiFfhz02#T$Y~T-<1L?xr;_MPGS8Bw5?m)ZPpr#3 zWJ^4e^eEZj0*TDSK&KMtOI=Xk^-c{gw`xa!%|~3tA*XPl$MuJGRKh z`6-YI@xRi8;N|M_7diyvNESj6qKTc5zfZ6OE3%H6pU-0nNi>ZH?ksiImrx2yd=E~t&xQRNKFsq zME-t)L{x>4_hd+x?7c5p!xW6c%ez#_hy2BWGK^ZPdQyXwikajRMC#M>E5UCGSV1C; zm&mwec!cxPC2Ft&M=)q|bXj7u0N;ovSn%BKd@86TVSR2YNB<+BK~R-g6d6Grqfh7~ zv$<|OR}_6<`4TH61>a`{-x=9!e9}cMiu|P(i2?*ONCY8SuE)&p z2gdi>(Q1j+BB^M=VOY+90kfmBkPi<(a zfi}orTc`nLb@)W=OIzej>`enGm0X0>8Rc&|WP)0WbKj$jlAo7;#;sV4wbUPGV?QR- z-6C1oUR2dOfo%GNbfmV{9gH)8L;6HzWzH2kxWS@v_R^oK4d_!wM5E-izOzJ zxZ@qiM`x}@F68gv2uhJo%zHeuFBtPJy&Mmib@5qGopU5F7QluJYLvgnA|2z>doA*r z2@Xk?USuLa{1n>~iN%r3XM+5egu+UETZnTdzb|@|1PbLLv5i<4$rV3#aspDBl76n! z*a^uutp*cr=dr_)44MBGa7(1)Fq8`FI?LI|nGMMqNUzFiK9xiK=V@m&Qis3HH#d-D z`3oUpaRrrWa9B{Q8$O9;lX{%Uj>MRf|BzD(67hqgP8JFa7 z3OE>{G*TsheW;j&m;}p94~gt#Ao&uxmxup-JQ6u*BOK|M)veMC_6quLqLasvVyR#4 zz^Y28bS;(j70BRHj>S|27IR1k`yRAI@{!j$Bo33m10zwL z{M94LV@gk10dzsCC(<1vv4Hg2N>ox6X%bx(j9-{fq$(-BXC*mLa@LZslGr~PoRIvA zygJjj8Zb| zpSW&9f4RialJgdd1aaj<3raEnh2d6CxF&tdwZR6mkA-w8cXjGMW55g22i%j$@JG;4 zGxWF)l2eA!72tOX-Lo$InPi6&I%sWXPdY+EL_;DsDw-s5v>=B2Nc%zfb{g&-M?$us zBirz{*BG7DE~J(v5xwL}rK{{JoV|(%IK%nJoVemD&yTpC@Jw_>@@$gFl6+POnGx?S z-Yy|8Bl?jLrAtOZvi*6HC8;+{zEQlT)b*1w5287V93&z685e)^Le@Zw1+Ks-6FVya zt~=T(wYgYl?a)YiW!rL=4ZU9Gya3#5iA65U6+(6@tWSTiKn&HbrdZx=AhA940wzDEW!j ztjw;de#FLgWi9m?_6)ehxL+XG3y{K{N=sITbyhd=eI9JFtZ@31d5usmu#docr6H^O zEY`)v(*--8xh=~0Ua-H}9;Gnrg|73PQLF{{M2D`$j#~xs2|;@%9sT1J=8Kh{QS1@m zVV~i@&4sKe*V!Ly9P22zvTkyZm5k2&K6Lu7V)wKPye|r#y}?@VW7af#q4EcNc+O&H z{`&mIp0jpscC+xZ|Iafun|g))_e!w7_Oa&Co>D7_SAyz$ZMnXioqrl@!&!Ir8~ZV^ zhJd|4mRlRG%tYiH;CwCm4rXBs=CVGlu(h1M(QdMi`7--I+~cp>Y%<>&LySnHxv|;E zX!c}*iw^)aeRSC1RS|8c9umMu_346U3F8-@jXaD*q+Ftz?>&5T0%C|Cm z%KXcU=1p1#eTAOX6=bKsIjkTru3yu7u@Buyc5G{be~v^7is1FeQak+tsTpkVWsT-- z_WL``nq`f(juTloo5>o>ZkA<2wb?1U4143;4XrS;uoKh;c0HZKE>Z8`;T6_I9%DzZ z9qfVDf!)tuux@iME8UMP&DnoEC%bKJ?nwNE(gSGS8KCPX0lN~k|G3L{7t|oosgRvZ2!6QqMtXs_LZ)Se;9Bb7}8`+Kg z?C$q7Jc~Bcvs2+@^QifZJt~qi^Uql!n3h$9C;3YkiCF=Yg0)}abl46F%xJ)GXN_tbB!1We0bDzaa|VgAmA>|l|XuI*Sl zxnHu5_AL32*4RuN3%dw3(jOVT#G1a0)>pW6n!gZ|ot4K~t#H;ZCboj?N1?EfToTqY zR$#T|VpjZl_(d)BY$CsxzOFOi#5g=k6Y5MmSQAu$)zK?hCx4cG3{J3?bp#YtW;cfW z^t%rMyALPko9(P48jgO{#DmsBW^3U?eXNW-hZX(<{8X0poKINOJ)Ip);1hs70na&0=dR3S?e$r>l{g>xt|()Td>_& zaNdIdYw2E~wH&iJfOnW~6H~-uk>%3Dkd({Nj7psv#c}P1NGnOElu(hRi|&$2rK3?u zH={Ik)uf~}Oh_}i47r3*F0*P}nl zy#82@R^OGUDm}Y%*$F)#iXJP5oM)yp%1YH^R^Ua1`IXvG`1D zNY@h1wDvt&r^sr{RlchqM9jOc7QaiXeJCD0#?Hgr>8U2WxxN&Kk*)$?;5)MJ!~8IQGqpDV5G5MOm#?O&hw!Xd z;%l$RL3$N8X&3I-_jm&>@ogu<%|-akPwCRJR4;;Xx1aa-qxz~$@!C-DX^*2@0*Mxp zyCbZBhn8+vzt)T&d{;1pbLYw7te}h+w|2D0O>OM>HM?!>?=oG{Zg#JNL0IOG;^l7B z<285ExRXb|0EudgQ(ft05o`F7WQ{P@&c%Q~ACt2ST%Ux=|A{^eZSb*gbX=^j;W4_R zeTuO?6|?yUv7tf9ZoJ%q;@2VWqPAGBaJQ|zysc2QIj_{#~n|%$d&lNhpV9`f3g1hVYI}bXSXQmSV?_#^znGBgig2 zTn>~R0eg^}iU!0jCJ~kJvOLTd7&bZ$Smrl_s zVY_HDOV`UGl{aE5m(tonm0x4Fuh%bZiO#4S^+jlazuZTkrzN=Ld)Yxl)^eF^oJz*+ zvZ!NtY=7&lPaEskG20JR`xV|_LmIpiPkFMt%B;j=-;G)Q8Eq~zy8WuU& zzK>5%mFH}dja8SAp2LSaz?YFKZ63GoyNU&F<;P#Zq{?cC;q4w?bY`k4Ht%Meud&f> zJbfdU%OZ$0$g08xvV?kg9$%XOWWV?&Rx=TroDGfk;SleHfA2W98nw4{F9^%{Km2^Z zM-9Hx5F&Ku9S`{2i!#g4QL>s{I7wQ{vC=A2z* zs~ur=d)aMjzpLt>GfOS8O8uGH<3>5v!)lGjLAaq1bC4PFeQbNONHYj#WSmEf9HYz# zMmX+=qG9vgZzLB(sO)8=66q&?dK9g-ch>8Rvv+ajeLTOxU)hHb>Xmwa8M(Va!f;dv zia>X$kj(XTGgnSr$;>k=4yVdePm!}8R%Fvzy{)jmlRI{vT;$r>PfF#@cO>|L26s3< za!qd=#|^Z)8rEf2l{+c_OU@RKQ9UbGsnQ5vAhkNpG;!N+%%&e}AHw=TX$dh{{KV!9R0-fHX z3ced_3v0ZCh|}%d)G~B<-7B){ysC>%;qV!vW8_R>wn+up|-O} zJDG8uK#ElNHgW#e^w+~IWvDnZM$PQRVuvL?Kt{-TXLxlyKkiBU=dhRHibyZNUKR%0lj88<}xEYnHW%^@c$n zM%RDE$<#L->qtF#X0KV15AXY0_BsZl&n#GUv&EnhWE{qR2e^X7!?2uAH&TZhlOKy2 z&JN$4z3**C_i6eZOIz8U8b`DDU=$1>VNXB9&7A-NW_#BTE1oLu)fUx$5(oE) zCa)RqDoY#)tuE%vC&7l#;qZK8G~QVCqPbGpLJKU%6G>ZBc32^U+=t=*CFyqSpZ1l$ z6<@eVXiXg;&XhHF;m1R*u>Xm@zQfmZt z$JDGTXFSq9t zO#O6_?@oRbZgFo2lbwhBzV83e@8VH?y_)^!-ZXd}IZNT@84$FAh>*&TkDcLj5+?eF z{j;53voE&Mv+aI|2b4}bVMd3IR#i7123qz+!ePnY+;I@>cdooQeGH+ya++HXO~S*T zMUUmy8Nwf#TreW1`uPwv9PhpTi#4XmmbL6uu%B!kQbU$Ip&uxs%DzH};U}fq|K(zZ z>k0Y9Br$oU2%WjgnBsU4E``@qPSaU|tm@PBAr$?T9)pY7w@6*uam5`pY}mAj`zw4R~6nxJGOCVAzQcrC8T@b_inM)nLYOfy!{2*%FVC69=AI}j z2A0UV(p&WvUbIzyxD86~kg2`FQ-a_9f=`==NCkZnnRJq#K^&kwr7g)Dg$|%XFqwC~6kI5iM8S%{RyFt+0ZMqq5cvDg5ObFE6-(fagYkllC@q45^ zb2vGtvDc@Id203vsxr%Z@`q-HwEbAXy=-w7^h_T9A`E&-Onez0tr8($WdmEyaMpXi zngz@+v^|_&Z)3Bi^0%gRTccS2ez#adUJd2LmY&2)@0NF6W4_|9|7x0Yx1fxC; z0?hV!1a{6~6}eC52>)&>UR6wmE@ss8bVL%TeOZ3~3 zWLJsqml^d-Wtf*ct}!?3T(B;0VKIZD!wmMC75?l|Yaw_3IA&t{h@y+bxZ z&&?!XA@*lqWDY6j!mOZt9<#|c!()n9XS%xVo-Bm};W({@C)qzw)qLvJf_ks>i&QCB z)%>OEwH5?Vb!b+ytNSZ6oz#`4S|$9E zJ&b`cptB2CQT)!X+`+yvyL-{p$*#4D*w~R(br(l+Klh8x7gCdTneomZ-<4)-;c~Y3 zzOc>P@sYD>_Y9wG0&&A%4m+>J%qF|ix$AroK0D8mH3^+YSKh_zggvP=?(~kW&GiGJv>dV&R6}{M7nGsCB zU9}e(t4pt_#FRLd?u%*MysbWW^ zb4+;KsmIR_XX+ao(M1~w)RjFAVol*w&VWGkW#CU2$4W@E+I)Kh%-IO#R~Pf?`6B2% zxSo0S2-2sTEO9)26i#-%he4p~)+EE)P3}7vW=?@CS!?VKYr64{t6=999-Vo}4g4c@ zZM{6w(QA;LZG_nQh{zcWTp>1uft1*Luo0_IqfJO&2K8>_2lw!eu~0p^anO9a$29(& z%C?EF>VE%^rn|e=RGL)n7Bwh*B%J*{@bUxJyoI#O3#*?%-h0h!ZW9+HUA+3D;<&iT z-ml`>op^gU65ryTV++4o=vtSGoZI+DvXXtIts}0d^6U4;J$o^!3QWhUF|anX1#GIBE;o|tqmuQEaIprF zb|f8+bxeSm!Oe0~=ib8U7BdT;rV^b0z{|^*#n$Z9N literal 0 HcmV?d00001 diff --git a/etc/samples_floppy/35_step_1_1.wav b/etc/samples_floppy/35_step_1_1.wav new file mode 100644 index 0000000000000000000000000000000000000000..d45a399956dd70fdb02c822b0c52afad54bd9f2c GIT binary patch literal 6878 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 a9SY@Yf~dmLA)jC$9z>rTYbefxh~xjo3ws0r literal 0 HcmV?d00001 diff --git a/etc/samples_floppy/README.md b/etc/samples_floppy/README.md index 9fc7eb8d..5087f00e 100644 --- a/etc/samples_floppy/README.md +++ b/etc/samples_floppy/README.md @@ -1,7 +1,12 @@ Source: https://github.com/mamedev/mame/tree/master/samples/floppy +Note that the list of WAVs to include in the assets folder is picked +up by a cmake glob command. If files are added or removed then you'll +have to redo `make init` or (quicker...) touch `src/b2/CMakeLists.txt` +and recompile. + # **Samples** # Samples are taken by team members or contributors to make mechanical sounds of various machines available in emulation. -Licensed under [CC0 1.0 Universal (CC0 1.0)](https://creativecommons.org/publicdomain/zero/1.0/) \ No newline at end of file +Licensed under [CC0 1.0 Universal (CC0 1.0)](https://creativecommons.org/publicdomain/zero/1.0/) diff --git a/src/b2/b2.cpp b/src/b2/b2.cpp index d277cac6..76df251a 100644 --- a/src/b2/b2.cpp +++ b/src/b2/b2.cpp @@ -981,6 +981,17 @@ static void LoadDiscDriveSound(bool *good, DiscDriveType type, DiscDriveSound so static bool LoadDiscDriveSamples(Messages *init_messages) { bool good = true; + LoadDiscDriveSound(&good, DiscDriveType_90mm, DiscDriveSound_Seek2ms, "35_seek_2ms.wav", init_messages); + LoadDiscDriveSound(&good, DiscDriveType_90mm, DiscDriveSound_Seek6ms, "35_seek_6ms.wav", init_messages); + LoadDiscDriveSound(&good, DiscDriveType_90mm, DiscDriveSound_Seek12ms, "35_seek_12ms.wav", init_messages); + LoadDiscDriveSound(&good, DiscDriveType_90mm, DiscDriveSound_Seek20ms, "35_seek_20ms.wav", init_messages); + LoadDiscDriveSound(&good, DiscDriveType_90mm, DiscDriveSound_SpinEmpty, "35_spin_empty.wav", init_messages); + LoadDiscDriveSound(&good, DiscDriveType_90mm, DiscDriveSound_SpinEnd, "35_spin_end.wav", init_messages); + LoadDiscDriveSound(&good, DiscDriveType_90mm, DiscDriveSound_SpinLoaded, "35_spin_loaded.wav", init_messages); + LoadDiscDriveSound(&good, DiscDriveType_90mm, DiscDriveSound_SpinStartEmpty, "35_spin_start_empty.wav", init_messages); + LoadDiscDriveSound(&good, DiscDriveType_90mm, DiscDriveSound_SpinStartLoaded, "35_spin_start_loaded.wav", init_messages); + LoadDiscDriveSound(&good, DiscDriveType_90mm, DiscDriveSound_Step, "35_step_1_1.wav", init_messages); + LoadDiscDriveSound(&good, DiscDriveType_133mm, DiscDriveSound_Seek2ms, "525_seek_2ms.wav", init_messages); LoadDiscDriveSound(&good, DiscDriveType_133mm, DiscDriveSound_Seek6ms, "525_seek_6ms.wav", init_messages); LoadDiscDriveSound(&good, DiscDriveType_133mm, DiscDriveSound_Seek12ms, "525_seek_12ms.wav", init_messages); From e506a53040705f7013c8f1fa95156393762012c9 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Fri, 1 Nov 2024 00:27:07 +0000 Subject: [PATCH 13/31] Fix some debug command actioning stuff. Fuk #372. --- src/b2/b2.cpp | 1 + src/b2/debugger.cpp | 53 +++++++++++++++++++++------------------------ 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/b2/b2.cpp b/src/b2/b2.cpp index 76df251a..31e29eec 100644 --- a/src/b2/b2.cpp +++ b/src/b2/b2.cpp @@ -337,6 +337,7 @@ void *b2VBlankHandler::AllocateDisplayData(uint32_t display_id) { ////////////////////////////////////////////////////////////////////////// void b2VBlankHandler::FreeDisplayData(uint32_t display_id, void *data) { + (void)data; (void)display_id; LockGuard lock(m_mutex); diff --git a/src/b2/debugger.cpp b/src/b2/debugger.cpp index 00fa761f..9b9c6982 100644 --- a/src/b2/debugger.cpp +++ b/src/b2/debugger.cpp @@ -951,7 +951,7 @@ class M6502DebugWindow : public DebugUI { } //ImGui::Text("m_dso=0x%" PRIx32, m_dso); - if (m_cst.WasActioned(g_reset_relative_cycles_command)) { + if (this->cst->WasActioned(g_reset_relative_cycles_command)) { m_beeb_thread->Send(std::make_shared([dso = m_dso](BBCMicro *m) -> void { m->DebugResetRelativeCycleBase(dso); })); @@ -975,8 +975,8 @@ class M6502DebugWindow : public DebugUI { relative_cycles = absolute_cycles - m_beeb_state->DebugGetCPUCycless(m_dso, relative_base); } - m_cst.SetTicked(g_toggle_reset_relative_cycles_on_breakpoint_command, reset_on_breakpoint); - if (m_cst.WasActioned(g_toggle_reset_relative_cycles_on_breakpoint_command)) { + this->cst->SetTicked(g_toggle_reset_relative_cycles_on_breakpoint_command, reset_on_breakpoint); + if (this->cst->WasActioned(g_toggle_reset_relative_cycles_on_breakpoint_command)) { m_beeb_thread->Send(std::make_shared([dso = m_dso](BBCMicro *m) -> void { m->DebugToggleResetRelativeCycleBaseOnBreakpoint(dso); })); @@ -1012,14 +1012,12 @@ class M6502DebugWindow : public DebugUI { ImGui::Text("Relative cycles = %s", cycles_str); ImGui::SameLine(); - m_cst.DoButton(g_reset_relative_cycles_command); + this->cst->DoButton(g_reset_relative_cycles_command); ImGui::SameLine(); - m_cst.DoToggleCheckbox(g_toggle_reset_relative_cycles_on_breakpoint_command); + this->cst->DoToggleCheckbox(g_toggle_reset_relative_cycles_on_breakpoint_command); } private: - CommandStateTable m_cst; - void Reg(const char *name, uint8_t value) { ImGui::Text("%s = $%02x %03d %s", name, value, value, BINARY_BYTE_STRINGS[value]); } @@ -1401,8 +1399,8 @@ class DisassemblyDebugWindow : public DebugUI, // m->DebugGetMemBigPageIsMOSTable(pc_is_mos, m_dso); //} - m_cst.SetTicked(g_toggle_track_pc_command, m_track_pc); - if (m_cst.WasActioned(g_toggle_track_pc_command)) { + this->cst->SetTicked(g_toggle_track_pc_command, m_track_pc); + if (this->cst->WasActioned(g_toggle_track_pc_command)) { m_track_pc = !m_track_pc; if (m_track_pc) { @@ -1411,41 +1409,41 @@ class DisassemblyDebugWindow : public DebugUI, } } - m_cst.SetEnabled(g_back_command, !m_history.empty()); - if (m_cst.WasActioned(g_back_command)) { + this->cst->SetEnabled(g_back_command, !m_history.empty()); + if (this->cst->WasActioned(g_back_command)) { ASSERT(!m_history.empty()); m_track_pc = false; m_addr = m_history.back(); m_history.pop_back(); } - m_cst.SetEnabled(g_up_command, !m_track_pc); - if (m_cst.WasActioned(g_up_command)) { + this->cst->SetEnabled(g_up_command, !m_track_pc); + if (this->cst->WasActioned(g_up_command)) { this->Up(cpu->config, 1); } - m_cst.SetEnabled(g_down_command, !m_track_pc); - if (m_cst.WasActioned(g_down_command)) { + this->cst->SetEnabled(g_down_command, !m_track_pc); + if (this->cst->WasActioned(g_down_command)) { this->Down(cpu->config, 1); } - m_cst.SetEnabled(g_page_up_command, !m_track_pc); - if (m_cst.WasActioned(g_page_up_command)) { + this->cst->SetEnabled(g_page_up_command, !m_track_pc); + if (this->cst->WasActioned(g_page_up_command)) { this->Up(cpu->config, m_num_lines - 2); } - m_cst.SetEnabled(g_page_down_command, !m_track_pc); - if (m_cst.WasActioned(g_page_down_command)) { + this->cst->SetEnabled(g_page_down_command, !m_track_pc); + if (this->cst->WasActioned(g_page_down_command)) { this->Down(cpu->config, m_num_lines - 2); } - m_cst.SetEnabled(g_step_over_command, m_beeb_window->DebugIsRunEnabled()); - if (m_cst.WasActioned(g_step_over_command)) { + this->cst->SetEnabled(g_step_over_command, m_beeb_window->DebugIsRunEnabled()); + if (this->cst->WasActioned(g_step_over_command)) { m_beeb_window->DebugStepOver(m_dso); } - m_cst.SetEnabled(g_step_in_command, m_beeb_window->DebugIsRunEnabled()); - if (m_cst.WasActioned(g_step_in_command)) { + this->cst->SetEnabled(g_step_in_command, m_beeb_window->DebugIsRunEnabled()); + if (this->cst->WasActioned(g_step_in_command)) { m_beeb_window->DebugStepIn(m_dso); } @@ -1477,9 +1475,9 @@ class DisassemblyDebugWindow : public DebugUI, ImGui::SameLine(); this->WordRegUI("PC", cpu->opcode_pc); ImGui::SameLine(); - m_cst.DoToggleCheckbox(g_toggle_track_pc_command); + this->cst->DoToggleCheckbox(g_toggle_track_pc_command); - m_cst.DoButton(g_back_command); + this->cst->DoButton(g_back_command); if (ImGui::InputText("Address", m_address_text, sizeof m_address_text, @@ -1490,9 +1488,9 @@ class DisassemblyDebugWindow : public DebugUI, } } - m_cst.DoButton(g_step_over_command); + this->cst->DoButton(g_step_over_command); ImGui::SameLine(); - m_cst.DoButton(g_step_in_command); + this->cst->DoButton(g_step_in_command); if (m_track_pc) { if (m_beeb_debug_state && m_beeb_debug_state->is_halted) { @@ -1786,7 +1784,6 @@ class DisassemblyDebugWindow : public DebugUI, int m_num_lines = 0; //char m_disassembly_text[100]; float m_wheel = 0; - CommandStateTable m_cst; static bool IsBranchTaken(M6502Condition condition, M6502P p) { switch (condition) { From 5c95baae0ce22ccff712805addf969335f1b21af Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Fri, 1 Nov 2024 00:56:24 +0000 Subject: [PATCH 14/31] Fix easy stuff from CI warnings. Fix #381. --- src/b2/BeebWindow.cpp | 10 ++++++---- src/b2/misc.inl | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/b2/BeebWindow.cpp b/src/b2/BeebWindow.cpp index 4540d4e3..b9e7d6b4 100644 --- a/src/b2/BeebWindow.cpp +++ b/src/b2/BeebWindow.cpp @@ -1134,10 +1134,12 @@ bool BeebWindow::DoImGui(uint64_t ticks) { handled = this->HandleBeebKey(event.keysym, state); } - if (!m_settings.prefer_shortcuts) { - m_cst.ActionCommands(beeb_window_commands); - if (active_popup_commands) { - active_popup->cst->ActionCommands(active_popup_commands); + if (!handled) { + if (!m_settings.prefer_shortcuts) { + m_cst.ActionCommands(beeb_window_commands); + if (active_popup_commands) { + active_popup->cst->ActionCommands(active_popup_commands); + } } } } else { diff --git a/src/b2/misc.inl b/src/b2/misc.inl index 444b8dd1..64175344 100644 --- a/src/b2/misc.inl +++ b/src/b2/misc.inl @@ -1,6 +1,6 @@ #define ENAME BBCUTF8ConvertMode EBEGIN() -// Pass all values through. BBC £ will come through as `. +// Pass all values through. BBC pound sign will come through as `. EPN(PassThrough) // Translate to teletext chars. [ will come through as left arrow, etc. From 7ea2524514684c4edc24d1515b7ac4710687c248 Mon Sep 17 00:00:00 2001 From: Tom Seddon <-> Date: Sat, 16 Nov 2024 23:05:40 +0000 Subject: [PATCH 15/31] Fix a couple of Windows things. * Remove _WIN32_WINNT check, which was interfering with the libretro port. The test can go elsewhere, if it's even still necessary (which I suspect it isn't) * Add SCODE-to-DWORD casts. Hopefully fix warning encountered on gcc --- src/shared/c/system_windows.cpp | 4 ++-- src/shared/h/shared/system_windows.h | 9 --------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/shared/c/system_windows.cpp b/src/shared/c/system_windows.cpp index c01977a2..29c2d984 100644 --- a/src/shared/c/system_windows.cpp +++ b/src/shared/c/system_windows.cpp @@ -120,8 +120,8 @@ const char *GetErrorDescription(DWORD error) { if (n == 0) { /* Bleargh. */ -#define CASE(X) \ - case (X): \ +#define CASE(X) \ + case (DWORD)(X): \ return #X switch (error) { diff --git a/src/shared/h/shared/system_windows.h b/src/shared/h/shared/system_windows.h index e5f09515..fe76ba65 100644 --- a/src/shared/h/shared/system_windows.h +++ b/src/shared/h/shared/system_windows.h @@ -7,15 +7,6 @@ ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -#ifdef _WIN32_WINNT -// 0x600 = Vista+; 0x601 = Win7+; 0x602 = Win8+; -#if _WIN32_WINNT < 0x601 -#error _WIN32_WINNT should probably be 0x601 or better -#endif -#else -#define _WIN32_WINNT 0x601 -#endif - #define NOMINMAX #define WIN32_LEAN_AND_MEAN #include From 3666661f03fab7db551cccf32e6bcc0319e0848f Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sat, 2 Nov 2024 21:54:58 +0000 Subject: [PATCH 16/31] Add note about ninja -j 1. See https://www.stardot.org.uk/forums/viewtopic.php?p=437981#p437981 --- doc/Building-on-Unix.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/Building-on-Unix.md b/doc/Building-on-Unix.md index 48d689f0..bf07a4c6 100644 --- a/doc/Building-on-Unix.md +++ b/doc/Building-on-Unix.md @@ -83,7 +83,10 @@ Day-to-day build steps: 2. Run `ninja` to build. If building with gcc, note that it is normal for this to take longer than you might like. Build times with clang - are a bit better + are a bit better. It may use a lot of RAM in either case + + (`ninja -j 1` will run max 1 job at once, possibly worth trying on + Linux if the build awakens the OOM killer) 3. Run `ninja test` to run the automated tests (this might take a few minutes - they should all pass) From d04d482747997838d975b88527fe195fe96a910b Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sun, 17 Nov 2024 15:20:18 +0000 Subject: [PATCH 17/31] Fix minor path_posix int/bool mixup. --- src/shared/c/path_posix.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/c/path_posix.cpp b/src/shared/c/path_posix.cpp index 5866bd11..513f64b9 100644 --- a/src/shared/c/path_posix.cpp +++ b/src/shared/c/path_posix.cpp @@ -24,7 +24,7 @@ bool PathGlob(const std::string &folder, while ((de = readdir(d)) != NULL) { std::string path = PathJoined(folder, de->d_name); - int is_folder = PathIsFolderOnDisk(path); + bool is_folder = PathIsFolderOnDisk(path); fun(path, is_folder); } From c6f59a647447457b47440b181039087ef3a2e538 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sun, 17 Nov 2024 15:34:13 +0000 Subject: [PATCH 18/31] Start on ffmpeg tidy-up. * Remove requirement for specific version. Use #if version checks to work around the (not very significant) differences * Fix up some stuff in test_ffmpeg, which had got extremely stale --- CMakeLists.txt | 58 ++-- doc/b2_notes.org | 330 +++++++++++++++++++++++ experimental/CMakeLists.txt | 2 +- experimental/test_ffmpeg/CMakeLists.txt | 4 +- experimental/test_ffmpeg/test_ffmpeg.cpp | 148 ++++++++-- experimental/test_ffmpeg/test_ffmpeg.inl | 60 +++++ 6 files changed, 543 insertions(+), 59 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 300cb05a..1de122dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -274,45 +274,29 @@ if(LINUX OR OSX) else() set(FFMPEG__GOOD ON) - set(FFMPEG__AVCODEC_VERSION 58) - set(FFMPEG__AVFORMAT_VERSION 58) - set(FFMPEG__AVUTIL_VERSION 56) - set(FFMPEG__SWRESAMPLE_VERSION 3) - set(FFMPEG__SWSCALE_VERSION 5) - - # https://gitlab.kitware.com/cmake/cmake/-/issues/18067 - ridiculous - if(NOT FFMPEG_libavcodec_VERSION MATCHES "^58\.") - message(WARNING "Unsupported libavcodec major version - need 58") - set(FFMPEG__GOOD OFF) - endif() - - if(NOT FFMPEG_libavformat_VERSION MATCHES "^58\.") - message(WARNING "Unsupported libavformat major version - need 58") - set(FFMPEG__GOOD OFF) - endif() - - if(NOT FFMPEG_libavutil_VERSION MATCHES "^56\.") - message(WARNING "Unsupported libavutil major version - need 56") - set(FFMPEG__GOOD OFF) - endif() - - if(NOT FFMPEG_libswresample_VERSION MATCHES "^3\.") - message(WARNING "Unsupported libswreasmple major version - need 3") - set(FFMPEG__GOOD OFF) - endif() - - if(NOT FFMPEG_libswscale_VERSION MATCHES "^5\.") - message(WARNING "Unsupported libswscale major version - need 5") - set(FFMPEG__GOOD OFF) - endif() - if(FFMPEG__GOOD) - message(STATUS "FFmpeg 4 found") - message(STATUS " Libraries: ${FFMPEG_LIBRARIES}") - message(STATUS " Include dirs: ${FFMPEG_INCLUDE_DIRS}") - message(STATUS " Definitions: ${FFMPEG_DEFINITIONS}") + message(STATUS "FFmpeg found - ") + + message(STATUS " FFMPEG_LIBRARIES: ${FFMPEG_LIBRARIES}") + message(STATUS " FFMPEG_LINK_LIBRARIES: ${FFMPEG_LINK_LIBRARIES}") + message(STATUS " FFMPEG_LIBRARY_DIRS: ${FFMPEG_LIBRARY_DIRS}") + message(STATUS " FFMPEG_LDFLAGS: ${FFMPEG_LDFLAGS}") + message(STATUS " FFMPEG_LDFLAGS_OTHER: ${FFMPEG_LDFLAGS_OTHER}") + message(STATUS " FFMPEG_INCLUDE_DIRS: ${FFMPEG_INCLUDE_DIRS}") + message(STATUS " FFMPEG_CFLAGS: ${FFMPEG_CFLAGS}") + message(STATUS " FFMPEG_CFLAGS_OTHER: ${FFMPEG_CFLAGS_OTHER}") + + # message(STATUS " Libraries: ${FFMPEG_LIBRARIES}") + # message(STATUS " Library dirs: ${FFMPEG_LIBRARY_DIRS}") + # message(STATUS " Include dirs: ${FFMPEG_INCLUDE_DIRS}") + # message(STATUS " Definitions: ${FFMPEG_DEFINITIONS}") + message(STATUS " libavcodec version: ${FFMPEG_libavcodec_VERSION}") + message(STATUS " libavformat version: ${FFMPEG_libavformat_VERSION}") + message(STATUS " libavutil version: ${FFMPEG_libavutil_VERSION}") + message(STATUS " libswresample version: ${FFMPEG_libswresample_VERSION}") + message(STATUS " libswscale version: ${FFMPEG_libswscale_VERSION}") else() - message(WARNING "FFmpeg 4 required specifically - video writing not available") + message(WARNING "FFmpeg not found - video writing not available") set(FFMPEG_FOUND OFF) endif() endif() diff --git a/doc/b2_notes.org b/doc/b2_notes.org index 0ca99fa4..8651494d 100644 --- a/doc/b2_notes.org +++ b/doc/b2_notes.org @@ -1340,4 +1340,334 @@ debug: 2 min 29 sec Release: 5 min 30 sec +* ffmpeg7 + +For MacPorts, need this before =make init_xcode=: + +: export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/opt/local/libexec/ffmpeg7/lib/pkgconfig + +Component major version by ffmpeg major version: + +| lib | Define | ffmpeg4 | ...5 | ...6 | ...7 | +|------------+---------------------------+---------+------+------+------| +| avcodec | LIBAVCODEC_VERSION_MAJOR | 58 | 59 | 60 | 61 | +| avformat | LIBAVFORMAT_VERSION_MAJOR | 58 | 59 | 60 | 61 | +| avutil | LIBAVUTIL_VERSION_MAJOR | 56 | 57 | 58 | 59 | +| swresample | | 3 | | | 5 | +| swscale | | 5 | | | 8 | + +** Version changes + +AVCodec::ch_layouts - introduced in FFmpeg6, LIBAVCODEC_VERSION_MAJOR>=60 + +*** No member named 'codec' in 'AVStream' + +Changed in ffmpeg5. + +Use =codecpar= instead. + +*** No member named 'channels' in 'AVCodecContext' + +Changed in ffmpeg6. + +I _think_ this is covered by the nb_channels field in the new +AVChannelLayout struct... + +*** No member named 'channel_layout' in 'AVFrame'; No member named 'channel_layout' in 'AVCodecContext' + +Changed in ffmpeg6. + +Use ch_layout instead. + +: * Copying an AVChannelLayout via assigning is forbidden, +: * av_channel_layout_copy() must be used instead (and its return value should +: * be checked) + +int av_channel_layout_copy(AVChannelLayout *dst, const AVChannelLayout *src); + +Regarding =AV_CH_LAYOUT_MONO= and so on, there is +=AV_CHANNEL_LAYOUT_STEREO= (etc.), macros that expand to struct +literals. So this can be used to create fodder for the copy function. + +#+begin_src c++ + const AVChannelLayout LAYOUT_MONO=AV_CHANNEL_LAYOUT_MONO; + const AVChannelLayout LAYOUT_STEREO=AV_CHANNEL_LAYOUT_STEREO; + TEST_AV(av_channel_layout_copy(&ac_context->ch_layout,wav_fmt->nChannels==1?&LAYOUT_MONO:&LAYOUT_STEREO)); +#+end_src + +(The naming is annoyingly wonky.) + +** ffmpeg7 + +Audio codecs: + +012v 4xm 8bps a64multi a64multi5 aasc agm aic alias_pix alias_pix amv +amv anm ansi apng apng arbc argo asv1 asv1 asv2 asv2 aura aura2 av1 +avrn avrp avrp avs avui avui bethsoftvid bfi binkvideo bintext +bitpacked bitpacked bmp bmp bmv_video brender_pix c93 camstudio +camtasia cavs cdgraphics cdtoons cdxl cfhd cfhd cinepak cinepak +clearvideo cljr cljr cllc cpia cri cyuv dds dfa dirac dnxhd dnxhd dpx +dpx dsicinvideo dvvideo dvvideo dxa dxtory dxv dxv eacmv eamad eatgq +eatgv eatqi escape124 escape130 exr exr ffv1 ffv1 ffvhuff ffvhuff fic +fits fits flashsv flashsv flashsv2 flashsv2 flic flv flv fmvc fraps +frwu g2m gdv gem gif gif h261 h261 h263 h263 h263i h263p h263p h264 +h264_videotoolbox hap hdr hdr hevc hevc_videotoolbox hnm4video hq_hqa +hqx huffyuv huffyuv hymt idcinvideo idf iff imm4 imm5 indeo2 indeo3 +indeo4 indeo5 interplayvideo ipu jpeg2000 jpeg2000 jpegls jpegls jv +kgv1 kmvc lagarith lead libaom-av1 libaom-av1 libdav1d libopenjpeg +librsvg libsvtav1 libtheora libvpx libvpx libvpx-vp9 libvpx-vp9 +libwebp libwebp_anim libx264 libx264rgb libx265 libxvid ljpeg loco +lscr m101 magicyuv magicyuv mdec media100 mimic mjpeg mjpeg mjpegb +mmvideo mobiclip motionpixels mpeg1video mpeg1video mpeg2video +mpeg2video mpeg4 mpeg4 mpegvideo msa1 mscc msmpeg4 msmpeg4 msmpeg4v1 +msmpeg4v2 msmpeg4v2 msp2 msrle msrle mss1 mss2 msvideo1 msvideo1 mszh +mts2 mv30 mvc1 mvc2 mvdv mvha mwsc mxpeg notchlc nuv paf_video pam pam +pbm pbm pcx pcx pdv pfm pfm pgm pgm pgmyuv pgmyuv pgx phm phm photocd +pictor pixlet png png ppm ppm prores prores prores_aw prores_ks +prores_videotoolbox prosumer psd ptx qdraw qoi qoi qpeg qtrle qtrle +r10k r10k r210 r210 rasc rawvideo rawvideo rl2 roqvideo roqvideo rpza +rpza rscc rtv1 rv10 rv10 rv20 rv20 rv30 rv40 sanm scpr screenpresso +sga sgi sgi sgirle sheervideo simbiosis_imx smackvid smc smc smvjpeg +snow snow sp5x speedhq speedhq srgc sunrast sunrast svq1 svq1 svq3 +targa targa targa_y216 tdsc theora thp tiertexseqvideo tiff tiff tmv +truemotion1 truemotion2 truemotion2rt tscc2 txd ultimotion utvideo +utvideo v210 v210 v210x v308 v308 v408 v408 v410 v410 vb vble vbn vbn +vc1 vc1image vc2 vcr1 vmdvideo vmix vmnc vnull vnull vp3 vp4 vp5 vp6 +vp6a vp6f vp7 vp8 vp9 vqavideo vqc vvc wbmp wbmp wcmv webp wmv1 wmv1 +wmv2 wmv2 wmv3 wmv3image wnv1 wrapped_avframe wrapped_avframe xan_wc3 +xan_wc4 xbin xbm xbm xface xface xl xpm xwd xwd y41p y41p ylc yop yuv4 +yuv4 zerocodec zlib zlib zmbv zmbv + +Video codecs: + +8svx_exp 8svx_fib aac aac aac_at aac_at aac_fixed aac_latm ac3 ac3 +ac3_at ac3_fixed ac3_fixed acelp.kelvin adpcm_4xm adpcm_adx adpcm_adx +adpcm_afc adpcm_agm adpcm_aica adpcm_argo adpcm_argo adpcm_ct +adpcm_dtk adpcm_ea adpcm_ea_maxis_xa adpcm_ea_r1 adpcm_ea_r2 +adpcm_ea_r3 adpcm_ea_xas adpcm_ima_acorn adpcm_ima_alp adpcm_ima_alp +adpcm_ima_amv adpcm_ima_amv adpcm_ima_apc adpcm_ima_apm adpcm_ima_apm +adpcm_ima_cunning adpcm_ima_dat4 adpcm_ima_dk3 adpcm_ima_dk4 +adpcm_ima_ea_eacs adpcm_ima_ea_sead adpcm_ima_iss adpcm_ima_moflex +adpcm_ima_mtf adpcm_ima_oki adpcm_ima_qt adpcm_ima_qt adpcm_ima_qt_at +adpcm_ima_rad adpcm_ima_smjpeg adpcm_ima_ssi adpcm_ima_ssi +adpcm_ima_wav adpcm_ima_wav adpcm_ima_ws adpcm_ima_ws adpcm_ms +adpcm_ms adpcm_mtaf adpcm_psx adpcm_sbpro_2 adpcm_sbpro_3 +adpcm_sbpro_4 adpcm_swf adpcm_swf adpcm_thp adpcm_thp_le adpcm_vima +adpcm_xa adpcm_xmd adpcm_yamaha adpcm_yamaha adpcm_zork alac alac +alac_at alac_at als amr_nb_at amrnb amrwb anull anull apac ape aptx +aptx aptx_hd aptx_hd atrac1 atrac3 atrac3al atrac3plus atrac3plusal +atrac9 binkaudio_dct binkaudio_rdft bmv_audio bonk cbd2_dpcm +comfortnoise comfortnoise cook dca dca derf_dpcm dfpwm dfpwm dolby_e +dsd_lsbf dsd_lsbf_planar dsd_msbf dsd_msbf_planar dsicinaudio dss_sp +dst dvaudio eac3 eac3 eac3_at evrc fastaudio flac flac ftr g722 g722 +g723_1 g723_1 g726 g726 g726le g726le g729 gremlin_dpcm gsm gsm_ms +gsm_ms_at hca hcom iac ilbc ilbc_at ilbc_at imc interplay_dpcm +interplayacm libmp3lame libopus libopus libspeex libspeex libvorbis +libvorbis mace3 mace6 metasound misc4 mlp mlp mp1 mp1_at mp1float mp2 +mp2 mp2_at mp2fixed mp2float mp3 mp3_at mp3adu mp3adufloat mp3float +mp3on4 mp3on4float mpc7 mpc8 msnsiren nellymoser nellymoser on2avc +opus opus osq paf_audio pcm_alaw pcm_alaw pcm_alaw_at pcm_alaw_at +pcm_bluray pcm_bluray pcm_dvd pcm_dvd pcm_f16le pcm_f24le pcm_f32be +pcm_f32be pcm_f32le pcm_f32le pcm_f64be pcm_f64be pcm_f64le pcm_f64le +pcm_lxf pcm_mulaw pcm_mulaw pcm_mulaw_at pcm_mulaw_at pcm_s16be +pcm_s16be pcm_s16be_planar pcm_s16be_planar pcm_s16le pcm_s16le +pcm_s16le_planar pcm_s16le_planar pcm_s24be pcm_s24be pcm_s24daud +pcm_s24daud pcm_s24le pcm_s24le pcm_s24le_planar pcm_s24le_planar +pcm_s32be pcm_s32be pcm_s32le pcm_s32le pcm_s32le_planar +pcm_s32le_planar pcm_s64be pcm_s64be pcm_s64le pcm_s64le pcm_s8 pcm_s8 +pcm_s8_planar pcm_s8_planar pcm_sga pcm_u16be pcm_u16be pcm_u16le +pcm_u16le pcm_u24be pcm_u24be pcm_u24le pcm_u24le pcm_u32be pcm_u32be +pcm_u32le pcm_u32le pcm_u8 pcm_u8 pcm_vidc pcm_vidc qcelp qdm2 qdm2_at +qdmc qdmc_at qoa ralf real_144 real_144 real_288 rka roq_dpcm roq_dpcm +s302m s302m sbc sbc sdx2_dpcm shorten sipr siren smackaud sol_dpcm +sonic sonic sonicls speex tak truehd truehd truespeech tta tta twinvq +vmdaudio vorbis vorbis wady_dpcm wavarc wavesynth wavpack wavpack +wmalossless wmapro wmav1 wmav1 wmav2 wmav2 wmavoice ws_snd1 xan_dpcm +xma1 xma2 + +Formats: + +| name | long_name | mime_type | acodec | vcodec | scodec | flags | extensions | +|-------------------------+-------------------------------------------------------------+-------------------------------------------+------------------+-----------------+----------+--------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| a64 | a64 - video for Commodore 64 | (null) | (none) | a64multi | (none) | (none) | a64, A64 | +| ac3 | raw AC-3 | audio/x-ac3 | ac3 | (none) | (none) | (none) | ac3 | +| ac4 | raw AC-4 | audio/ac4 | (none) | (none) | (none) | (none) | ac4 | +| adts | ADTS AAC (Advanced Audio Coding) | audio/aac | aac | (none) | (none) | (none) | aac,adts | +| adx | CRI ADX | (null) | adpcm_adx | (none) | (none) | (none) | adx | +| aea | MD STUDIO audio | (null) | atrac1 | (none) | (none) | (none) | aea | +| aiff | Audio IFF | audio/aiff | pcm_s16be | png | (none) | (none) | aif,aiff,afc,aifc | +| alp | LEGO Racers ALP | (null) | adpcm_ima_alp | (none) | (none) | (none) | tun,pcm | +| amr | 3GPP AMR | audio/amr | amrnb | (none) | (none) | (none) | amr | +| amv | AMV | video/amv | adpcm_ima_amv | amv | (none) | (none) | amv | +| apm | Ubisoft Rayman 2 APM | (null) | adpcm_ima_apm | (none) | (none) | (none) | apm | +| apng | Animated Portable Network Graphics | image/png | (none) | apng | (none) | (none) | apng | +| aptx | raw aptX (Audio Processing Technology for Bluetooth) | (null) | aptx | (none) | (none) | (none) | aptx | +| aptx_hd | raw aptX HD (Audio Processing Technology for Bluetooth) | (null) | aptx_hd | (none) | (none) | (none) | aptxhd | +| argo_asf | Argonaut Games ASF | (null) | adpcm_argo | (none) | (none) | (none) | (null) | +| argo_cvg | Argonaut Games CVG | (null) | adpcm_psx | (none) | (none) | (none) | cvg | +| asf | ASF (Advanced / Active Streaming Format) | video/x-ms-asf | wmav2 | msmpeg4 | (none) | (none) | asf,wmv,wma | +| ass | SSA (SubStation Alpha) subtitle | text/x-ass | (none) | (none) | ssa | (none) | ass,ssa | +| ast | AST (Audio Stream) | (null) | pcm_s16be_planar | (none) | (none) | (none) | ast | +| asf_stream | ASF (Advanced / Active Streaming Format) | video/x-ms-asf | wmav2 | msmpeg4 | (none) | (none) | asf,wmv,wma | +| au | Sun AU | audio/basic | pcm_s16be | (none) | (none) | (none) | au | +| avi | AVI (Audio Video Interleaved) | video/x-msvideo | libmp3lame | mpeg4 | (none) | (none) | avi | +| avif | AVIF | image/avif | (none) | libaom-av1 | (none) | (none) | avif | +| avm2 | SWF (ShockWave Flash) (AVM2) | application/x-shockwave-flash | libmp3lame | flv | (none) | (none) | (null) | +| avs2 | raw AVS2-P2/IEEE1857.4 video | (null) | (none) | (none) | (none) | (none) | avs,avs2 | +| avs3 | AVS3-P2/IEEE1857.10 | (null) | (none) | (none) | (none) | (none) | avs3 | +| bit | G.729 BIT file format | audio/bit | g729 | (none) | (none) | (none) | bit | +| caf | Apple CAF (Core Audio Format) | audio/x-caf | pcm_s16be | (none) | (none) | (none) | caf | +| cavsvideo | raw Chinese AVS (Audio Video Standard) video | (null) | (none) | cavs | (none) | (none) | cavs | +| codec2 | codec2 .c2 muxer | (null) | (none) | (none) | (none) | (none) | c2 | +| codec2raw | raw codec2 muxer | (null) | (none) | (none) | (none) | (none) | (null) | +| crc | CRC testing | (null) | pcm_s16le | rawvideo | (none) | (none) | (null) | +| dash | DASH Muxer | (null) | aac | libx264 | (none) | (none) | mpd | +| data | raw data | (null) | (none) | (none) | (none) | (none) | (null) | +| daud | D-Cinema audio | (null) | pcm_s24daud | (none) | (none) | (none) | 302 | +| dfpwm | raw DFPWM1a | (null) | dfpwm | (none) | (none) | (none) | dfpwm | +| dirac | raw Dirac | (null) | (none) | vc2 | (none) | (none) | drc,vc2 | +| dnxhd | raw DNxHD (SMPTE VC-3) | (null) | (none) | dnxhd | (none) | (none) | dnxhd,dnxhr | +| dts | raw DTS | audio/x-dca | dca | (none) | (none) | (none) | dts | +| dv | DV (Digital Video) | (null) | pcm_s16le | dvvideo | (none) | (none) | dv | +| eac3 | raw E-AC-3 | audio/x-eac3 | eac3 | (none) | (none) | (none) | eac3,ec3 | +| evc | raw EVC video | (null) | (none) | (none) | (none) | (none) | evc | +| f4v | F4V Adobe Flash Video | application/f4v | aac | libx264 | (none) | (none) | f4v | +| ffmetadata | FFmpeg metadata in text | (null) | (none) | (none) | (none) | (none) | ffmeta | +| fifo | FIFO queue pseudo-muxer | (null) | (none) | (none) | (none) | (none) | (null) | +| filmstrip | Adobe Filmstrip | (null) | (none) | rawvideo | (none) | (none) | flm | +| fits | Flexible Image Transport System | (null) | (none) | fits | (none) | (none) | fits | +| flac | raw FLAC | audio/x-flac | flac | png | (none) | (none) | flac | +| flv | FLV (Flash Video) | video/x-flv | libmp3lame | flv | (none) | (none) | flv | +| framecrc | framecrc testing | (null) | pcm_s16le | rawvideo | (none) | (none) | (null) | +| framehash | Per-frame hash testing | (null) | pcm_s16le | rawvideo | (none) | (none) | (null) | +| framemd5 | Per-frame MD5 testing | (null) | pcm_s16le | rawvideo | (none) | (none) | (null) | +| g722 | raw G.722 | audio/G722 | g722 | (none) | (none) | (none) | g722 | +| g723_1 | raw G.723.1 | audio/g723 | g723_1 | (none) | (none) | (none) | tco,rco | +| g726 | raw big-endian G.726 ("left-justified") | (null) | g726 | (none) | (none) | (none) | (null) | +| g726le | raw little-endian G.726 ("right-justified") | (null) | g726le | (none) | (none) | (none) | (null) | +| gif | CompuServe Graphics Interchange Format (GIF) | image/gif | (none) | gif | (none) | (none) | gif | +| gsm | raw GSM | audio/x-gsm | gsm | (none) | (none) | (none) | gsm | +| gxf | GXF (General eXchange Format) | (null) | pcm_s16le | mpeg2video | (none) | (none) | gxf | +| h261 | raw H.261 | video/x-h261 | (none) | h261 | (none) | (none) | h261 | +| h263 | raw H.263 | video/x-h263 | (none) | h263 | (none) | (none) | h263 | +| h264 | raw H.264 video | (null) | (none) | libx264 | (none) | (none) | h264,264 | +| hash | Hash testing | (null) | pcm_s16le | rawvideo | (none) | (none) | (null) | +| hds | HDS Muxer | (null) | aac | libx264 | (none) | (none) | (null) | +| hevc | raw HEVC video | (null) | (none) | libx265 | (none) | (none) | hevc,h265,265 | +| hls | Apple HTTP Live Streaming | (null) | aac | libx264 | webvtt | (none) | m3u8 | +| iamf | Raw Immersive Audio Model and Formats | (null) | opus | (none) | (none) | (none) | iamf | +| ico | Microsoft Windows ICO | image/vnd.microsoft.icon | (none) | bmp | (none) | (none) | ico | +| ilbc | iLBC storage | audio/iLBC | ilbc_at | (none) | (none) | (none) | lbc | +| image2 | image2 sequence | (null) | (none) | mjpeg | (none) | (none) | bmp,dpx,exr,jls,jpeg,jpg,jxl,ljpg,pam,pbm,pcx,pfm,pgm,pgmyuv,phm,png,ppm,sgi,tga,tif,tiff,jp2,j2c,j2k,xwd,sun,ras,rs,im1,im8,im24,sunras,vbn,xbm,xface,pix,y,avif,qoi,hdr,wbmp | +| image2pipe | piped image2 sequence | (null) | (none) | mjpeg | (none) | (none) | (null) | +| ipod | iPod H.264 MP4 (MPEG-4 Part 14) | video/mp4 | aac | libx264 | (none) | (none) | m4v,m4a,m4b | +| ircam | Berkeley/IRCAM/CARL Sound Format | (null) | pcm_s16le | (none) | (none) | (none) | sf,ircam | +| ismv | ISMV/ISMA (Smooth Streaming) | video/mp4 | aac | libx264 | (none) | (none) | ismv,isma | +| ivf | On2 IVF | (null) | (none) | libvpx | (none) | (none) | ivf | +| jacosub | JACOsub subtitle format | text/x-jacosub | (none) | (none) | jacosub | (none) | jss,js | +| kvag | Simon & Schuster Interactive VAG | (null) | adpcm_ima_ssi | (none) | (none) | (none) | vag | +| latm | LOAS/LATM | audio/MP4A-LATM | aac | (none) | (none) | (none) | latm,loas | +| lrc | LRC lyrics | (null) | (none) | (none) | srt | (none) | lrc | +| m4v | raw MPEG-4 video | (null) | (none) | mpeg4 | (none) | (none) | m4v | +| md5 | MD5 testing | (null) | pcm_s16le | rawvideo | (none) | (none) | (null) | +| matroska | Matroska | video/x-matroska | vorbis | libx264 | ssa | (none) | mkv | +| matroska | Matroska Audio | audio/x-matroska | vorbis | (none) | (none) | (none) | mka | +| microdvd | MicroDVD subtitle format | text/x-microdvd | (none) | (none) | microdvd | (none) | sub | +| mjpeg | raw MJPEG video | video/x-mjpeg | (none) | mjpeg | (none) | (none) | mjpg,mjpeg | +| mlp | raw MLP | (null) | mlp | (none) | (none) | (none) | mlp | +| mmf | Yamaha SMAF | application/vnd.smaf | adpcm_yamaha | (none) | (none) | (none) | mmf | +| mov | QuickTime / MOV | (null) | aac | libx264 | (none) | (none) | mov | +| mp2 | MP2 (MPEG audio layer 2) | audio/mpeg | mp2 | (none) | (none) | (none) | mp2,m2a,mpa | +| mp3 | MP3 (MPEG audio layer 3) | audio/mpeg | libmp3lame | png | (none) | (none) | mp3 | +| mp4 | MP4 (MPEG-4 Part 14) | video/mp4 | aac | libx264 | (none) | (none) | mp4 | +| mpeg | MPEG-1 Systems / MPEG program stream | video/mpeg | mp2 | mpeg1video | (none) | (none) | mpg,mpeg | +| vcd | MPEG-1 Systems / MPEG program stream (VCD) | video/mpeg | mp2 | mpeg1video | (none) | (none) | (null) | +| mpeg1video | raw MPEG-1 video | video/mpeg | (none) | mpeg1video | (none) | (none) | mpg,mpeg,m1v | +| dvd | MPEG-2 PS (DVD VOB) | video/mpeg | mp2 | mpeg2video | (none) | (none) | dvd | +| svcd | MPEG-2 PS (SVCD) | video/mpeg | mp2 | mpeg2video | (none) | (none) | vob | +| mpeg2video | raw MPEG-2 video | (null) | (none) | mpeg2video | (none) | (none) | m2v | +| vob | MPEG-2 PS (VOB) | video/mpeg | mp2 | mpeg2video | (none) | (none) | vob | +| mpegts | MPEG-TS (MPEG-2 Transport Stream) | video/MP2T | mp2 | mpeg2video | (none) | (none) | ts,m2t,m2ts,mts | +| mpjpeg | MIME multipart JPEG | multipart/x-mixed-replace;boundary=ffmpeg | (none) | mjpeg | (none) | (none) | mjpg | +| mxf | MXF (Material eXchange Format) | application/mxf | pcm_s16le | mpeg2video | (none) | (none) | mxf | +| mxf_d10 | MXF (Material eXchange Format) D-10 Mapping | application/mxf | pcm_s16le | mpeg2video | (none) | (none) | (null) | +| mxf_opatom | MXF (Material eXchange Format) Operational Pattern Atom | application/mxf | pcm_s16le | dnxhd | (none) | (none) | mxf | +| null | raw null video | (null) | pcm_s16le | wrapped_avframe | (none) | (none) | (null) | +| nut | NUT | video/x-nut | vorbis | mpeg4 | (none) | (none) | nut | +| obu | AV1 low overhead OBU | (null) | (none) | libaom-av1 | (none) | (none) | obu | +| oga | Ogg Audio | audio/ogg | flac | (none) | (none) | (none) | oga | +| ogg | Ogg | application/ogg | vorbis | libtheora | (none) | (none) | ogg | +| ogv | Ogg Video | video/ogg | vorbis | libtheora | (none) | (none) | ogv | +| oma | Sony OpenMG audio | audio/x-oma | atrac3 | (none) | (none) | (none) | oma | +| opus | Ogg Opus | audio/ogg | opus | (none) | (none) | (none) | opus | +| alaw | PCM A-law | (null) | pcm_alaw | (none) | (none) | (none) | al | +| mulaw | PCM mu-law | (null) | pcm_mulaw | (none) | (none) | (none) | ul | +| vidc | PCM Archimedes VIDC | (null) | pcm_vidc | (none) | (none) | (none) | (null) | +| f64be | PCM 64-bit floating-point big-endian | (null) | pcm_f64be | (none) | (none) | (none) | (null) | +| f64le | PCM 64-bit floating-point little-endian | (null) | pcm_f64le | (none) | (none) | (none) | (null) | +| f32be | PCM 32-bit floating-point big-endian | (null) | pcm_f32be | (none) | (none) | (none) | (null) | +| f32le | PCM 32-bit floating-point little-endian | (null) | pcm_f32le | (none) | (none) | (none) | (null) | +| s32be | PCM signed 32-bit big-endian | (null) | pcm_s32be | (none) | (none) | (none) | (null) | +| s32le | PCM signed 32-bit little-endian | (null) | pcm_s32le | (none) | (none) | (none) | (null) | +| s24be | PCM signed 24-bit big-endian | (null) | pcm_s24be | (none) | (none) | (none) | (null) | +| s24le | PCM signed 24-bit little-endian | (null) | pcm_s24le | (none) | (none) | (none) | (null) | +| s16be | PCM signed 16-bit big-endian | (null) | pcm_s16be | (none) | (none) | (none) | (null) | +| s16le | PCM signed 16-bit little-endian | (null) | pcm_s16le | (none) | (none) | (none) | sw | +| s8 | PCM signed 8-bit | (null) | pcm_s8 | (none) | (none) | (none) | sb | +| u32be | PCM unsigned 32-bit big-endian | (null) | pcm_u32be | (none) | (none) | (none) | (null) | +| u32le | PCM unsigned 32-bit little-endian | (null) | pcm_u32le | (none) | (none) | (none) | (null) | +| u24be | PCM unsigned 24-bit big-endian | (null) | pcm_u24be | (none) | (none) | (none) | (null) | +| u24le | PCM unsigned 24-bit little-endian | (null) | pcm_u24le | (none) | (none) | (none) | (null) | +| u16be | PCM unsigned 16-bit big-endian | (null) | pcm_u16be | (none) | (none) | (none) | (null) | +| u16le | PCM unsigned 16-bit little-endian | (null) | pcm_u16le | (none) | (none) | (none) | uw | +| u8 | PCM unsigned 8-bit | (null) | pcm_u8 | (none) | (none) | (none) | ub | +| psp | PSP MP4 (MPEG-4 Part 14) | (null) | aac | libx264 | (none) | (none) | mp4,psp | +| rawvideo | raw video | (null) | (none) | rawvideo | (none) | (none) | yuv,rgb | +| rcwt | RCWT (Raw Captions With Time) | (null) | (none) | (none) | cc_dec | (none) | bin | +| rm | RealMedia | application/vnd.rn-realmedia | ac3 | rv10 | (none) | (none) | rm,ra | +| roq | raw id RoQ | (null) | roq_dpcm | roqvideo | (none) | (none) | roq | +| rso | Lego Mindstorms RSO | (null) | pcm_u8 | (none) | (none) | (none) | rso | +| rtp | RTP output | (null) | pcm_mulaw | mpeg4 | (none) | (none) | (null) | +| rtp_mpegts | RTP/mpegts output format | (null) | aac | mpeg4 | (none) | (none) | (null) | +| rtsp | RTSP output | (null) | aac | mpeg4 | (none) | (none) | (null) | +| sap | SAP output | (null) | aac | mpeg4 | (none) | (none) | (null) | +| sbc | raw SBC | audio/x-sbc | sbc | (none) | (none) | (none) | sbc,msbc | +| scc | Scenarist Closed Captions | (null) | (none) | (none) | cc_dec | (none) | scc | +| film_cpk | Sega FILM / CPK | (null) | pcm_s16be_planar | cinepak | (none) | (none) | cpk | +| segment | segment | (null) | (none) | (none) | (none) | (none) | (null) | +| stream_segment,ssegment | streaming segment muxer | (null) | (none) | (none) | (none) | (none) | (null) | +| smjpeg | Loki SDL MJPEG | (null) | pcm_s16le | mjpeg | (none) | (none) | (null) | +| smoothstreaming | Smooth Streaming Muxer | (null) | aac | libx264 | (none) | (none) | (null) | +| sox | SoX (Sound eXchange) native | (null) | pcm_s32le | (none) | (none) | (none) | sox | +| spx | Ogg Speex | audio/ogg | libspeex | (none) | (none) | (none) | spx | +| spdif | IEC 61937 (used on S/PDIF - IEC958) | (null) | ac3 | (none) | (none) | (none) | spdif | +| srt | SubRip subtitle | application/x-subrip | (none) | (none) | srt | (none) | srt | +| streamhash | Per-stream hash testing | (null) | pcm_s16le | rawvideo | (none) | (none) | (null) | +| sup | raw HDMV Presentation Graphic Stream subtitles | application/x-pgs | (none) | (none) | pgssub | (none) | sup | +| swf | SWF (ShockWave Flash) | application/x-shockwave-flash | libmp3lame | flv | (none) | (none) | swf | +| tee | Multiple muxer tee | (null) | (none) | (none) | (none) | (none) | (null) | +| 3g2 | 3GP2 (3GPP2 file format) | (null) | amrnb | h263 | (none) | (none) | 3g2 | +| 3gp | 3GP (3GPP file format) | (null) | amrnb | h263 | (none) | (none) | 3gp | +| mkvtimestamp_v2 | extract pts as timecode v2 format, as defined by mkvtoolnix | (null) | (none) | rawvideo | (none) | (none) | (null) | +| truehd | raw TrueHD | (null) | truehd | (none) | (none) | (none) | thd | +| tta | TTA (True Audio) | audio/x-tta | tta | (none) | (none) | (none) | tta | +| ttml | TTML subtitle | text/ttml | (none) | (none) | ttml | (none) | ttml | +| uncodedframecrc | uncoded framecrc testing | (null) | pcm_s16le | rawvideo | (none) | (none) | (null) | +| vc1 | raw VC-1 video | (null) | (none) | vc1 | (none) | (none) | vc1 | +| vc1test | VC-1 test bitstream | (null) | (none) | wmv3 | (none) | (none) | rcv | +| voc | Creative Voice | audio/x-voc | pcm_s16le | (none) | (none) | (none) | voc | +| vvc | raw H.266/VVC video | (null) | (none) | vvc | (none) | (none) | vvc,h266,266 | +| w64 | Sony Wave64 | (null) | pcm_s16le | (none) | (none) | (none) | w64 | +| wav | WAV / WAVE (Waveform Audio) | audio/x-wav | pcm_s16le | (none) | (none) | (none) | wav | +| webm | WebM | video/webm | opus | libvpx-vp9 | webvtt | (none) | webm | +| webm_dash_manifest | WebM DASH Manifest | application/xml | (none) | (none) | (none) | (none) | xml | +| webm_chunk | WebM Chunk Muxer | video/webm | (none) | (none) | (none) | (none) | chk | +| webp | WebP | (null) | (none) | libwebp_anim | (none) | (none) | webp | +| webvtt | WebVTT subtitle | text/vtt | (none) | (none) | webvtt | (none) | vtt | +| wsaud | Westwood Studios audio | (null) | adpcm_ima_ws | (none) | (none) | (none) | aud | +| wtv | Windows Television (WTV) | (null) | ac3 | mpeg2video | (none) | (none) | wtv | +| wv | raw WavPack | audio/x-wavpack | wavpack | (none) | (none) | (none) | wv | +| yuv4mpegpipe | YUV4MPEG pipe | (null) | (none) | wrapped_avframe | (none) | (none) | y4m | + + * eof. diff --git a/experimental/CMakeLists.txt b/experimental/CMakeLists.txt index b78030ff..089600e9 100644 --- a/experimental/CMakeLists.txt +++ b/experimental/CMakeLists.txt @@ -7,7 +7,7 @@ if(WIN32) add_subdirectory(test_media_foundation) endif() -if(HAVE_FFMPEG) +if(FFMPEG_FOUND) add_subdirectory(test_ffmpeg) endif() diff --git a/experimental/test_ffmpeg/CMakeLists.txt b/experimental/test_ffmpeg/CMakeLists.txt index 74c16ed2..87ce6dbc 100644 --- a/experimental/test_ffmpeg/CMakeLists.txt +++ b/experimental/test_ffmpeg/CMakeLists.txt @@ -5,13 +5,13 @@ project(test_ffmpeg) ########################################################################## add_executable(test_ffmpeg - test_ffmpeg.cpp + test_ffmpeg.cpp test_ffmpeg.inl ) add_sanitizers(test_ffmpeg) target_include_directories(test_ffmpeg PRIVATE ${FFMPEG_INCLUDE_DIRS}) target_compile_definitions(test_ffmpeg PRIVATE ${FFMPEG_DEFINITIONS}) -target_link_libraries(test_ffmpeg PRIVATE shared_lib ${FFMPEG_LIBRARIES}) +target_link_libraries(test_ffmpeg PRIVATE shared_lib ${FFMPEG_LINK_LIBRARIES}) ########################################################################## ########################################################################## diff --git a/experimental/test_ffmpeg/test_ffmpeg.cpp b/experimental/test_ffmpeg/test_ffmpeg.cpp index 3b2f8ba5..b2d06743 100644 --- a/experimental/test_ffmpeg/test_ffmpeg.cpp +++ b/experimental/test_ffmpeg/test_ffmpeg.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #ifdef __clang__ @@ -54,6 +55,17 @@ static void PrintLastAVResult(const TestFailArgs *tfa) { ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// +//static std::string av_strerror(int result) { +// char tmp[AV_ERROR_MAX_STRING_SIZE]; +// +// av_strerror(result,tmp,sizeof tmp); +// +// return tmp; +//} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + #include #include "test_ffmpeg.inl" #include @@ -90,6 +102,8 @@ LOG_DEFINE(VOUT, "", &log_printer_stdout_and_debugger, false); LOG_DEFINE(OUT, "", &log_printer_stdout_and_debugger); LOG_DEFINE(ERR, "", &log_printer_stderr_and_debugger); +static const LogSet g_log_set={LOG(VOUT),LOG(OUT),LOG(ERR)}; + ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -216,7 +230,7 @@ static bool StoreWAVFile(const char *id, uint32_t size, const char *data, void * static bool LoadWAVFile(WAVFile *file, const std::string &file_name) { std::vector data; - if (!PathLoadBinaryFile(&data, file_name)) { + if (!LoadFile(&data, file_name, &g_log_set)) { return false; } @@ -233,6 +247,30 @@ static bool LoadWAVFile(WAVFile *file, const std::string &file_name) { ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// +template +static void PrintFlagsOneLine(T value,const char *(*get_name_fn)(T)) { + bool any=false; + + for(T mask=0;mask!=0;mask<<=1){ + const char *name=(*get_name_fn)(mask); + if(name[0]=='?'){ + continue; + } + + if(value&mask){ + if(any){ + LOGF(OUT," "); + } + LOG_STR(OUT,name); + any=true; + } + } + + if(!any){ + LOG_STR(OUT,"(none)"); + } +} + template static void PrintFlags(T value, const char *(*get_name_fn)(T)) { bool any = false; @@ -321,10 +359,30 @@ static void DumpCodecFormats(const T *fmts, } } +#if LIBAVCODEC_VERSION_MAJOR<60 static char *GetChannelLayoutString(char *buf, int n, uint64_t layout) { av_get_channel_layout_string(buf, n, -1, layout); return nullptr; } +#endif + +#if LIBAVCODEC_VERSION_MAJOR>=60 +static void DumpChLayouts(const AVCodec*c){ + LOGF(OUT,"Channel Layouts: "); + LOGI(OUT); + + if(!c->ch_layouts){ + LOGF(OUT,"NULL\n"); + }else{ + for(const AVChannelLayout*l=c->ch_layouts;l->order!=0;++l){ + LOGF(OUT, "%zd. ",l-c->ch_layouts); + LOGI(OUT); + LOGF(OUT,"order=%d (%s)\n",(int)l->order,GetAVChannelOrderEnumName(l->order)); + LOGF(OUT,"nb_channels=%d\n",l->nb_channels); + } + } +} +#endif static char *GetSampleRateString(char *buf, int n, int hz) { ASSERT(n >= 0); @@ -356,11 +414,19 @@ static void DumpCodecVerbose(const AVCodec *c) { true, (AVSampleFormat)-1); +#if LIBAVCODEC_VERSION_MAJOR>=60 + + DumpChLayouts(c); + +#else + DumpCodecFormats(c->channel_layouts, "Channel Layouts", &GetChannelLayoutString, false, (uint64_t)0); + +#endif DumpCodecFormats(c->supported_samplerates, "Sample Rates", @@ -394,6 +460,24 @@ static const char *GetAVCodecNameById(AVCodecID id) { return "(none)"; } +static void DumpFormatsOrg(){ + void *opaque = nullptr; + LOGF(OUT,"|name|long_name|mime_type|extensions|acodec|vcodec|scodec|flags|\n"); + LOGF(OUT,"|---\n"); + while (const AVOutputFormat *f = av_muxer_iterate(&opaque)) { + LOGF(OUT, "|%s ", f->name); + LOGF(OUT, "|%s", f->long_name); + LOGF(OUT, "|%s", f->mime_type); + LOGF(OUT, "|%s", f->extensions); + LOGF(OUT, "|%s", GetAVCodecNameById(f->audio_codec)); + LOGF(OUT, "|%s", GetAVCodecNameById(f->video_codec)); + LOGF(OUT, "|%s", GetAVCodecNameById(f->subtitle_codec)); + LOGF(OUT,"|"); + PrintFlagsOneLine(f->flags,&GetAVOutputFormatFlagEnumName); + LOGF(OUT,"\n"); + } +} + static void DumpFormatsVerbose() { void *opaque = nullptr; while (const AVOutputFormat *f = av_muxer_iterate(&opaque)) { @@ -451,6 +535,7 @@ struct Options { std::map options; int num_frames = 0; std::string audio_output_file_name; + bool org=false; }; static bool DoOptions(Options *o, int argc, char *argv[]) { @@ -459,6 +544,7 @@ static bool DoOptions(Options *o, int argc, char *argv[]) { // there used to be a way to fill this! - maybe it will come back // one day. std::vector options; + bool help; p.AddOption("version").Help("show version numbers").SetIfPresent(&o->version); p.AddOption("codecs").Help("dump codecs list").SetIfPresent(&o->dump_codecs); @@ -468,22 +554,28 @@ static bool DoOptions(Options *o, int argc, char *argv[]) { p.AddOption('a', "audio-codec").Meta("CODEC").Help("set audio codec").Arg(&o->acodec); p.AddOption('v', "video-codec").Meta("CODEC").Help("set video codec").Arg(&o->vcodec); p.AddOption('o').Meta("FILE").Help("write output to FILE").Arg(&o->output_file_name); + p.AddOption("org").Help("produce org-mode-friendly output when possible").SetIfPresent(&o->org); p.AddOption('n', "num-frames").Meta("COUNT").Help("write out max COUNT frames").Arg(&o->num_frames); - p.AddOption('A', "audio-output").Meta("FILE").Help("write raw audio data toFILE").Arg(&o->audio_output_file_name); + p.AddOption('A', "audio-output").Meta("FILE").Help("write raw audio data to FILE").Arg(&o->audio_output_file_name); + + p.AddHelpOption(&help); std::vector other_args; - if (!p.Parse(argc, argv, &other_args)) { + if (!p.Parse(argc, argv, &other_args)||help) { + p.Help(argv[0]); + + // not great to return exit code 1 on -h, but it's only test code return false; } - - if (other_args.size() != 1) { + + if (other_args.size() > 1) { LOGF(ERR, "Must specify exactly one folder\n"); return false; + } else if(other_args.size()==1){ + o->folder_name = other_args[0]; } - o->folder_name = other_args[0]; - if (o->num_frames < 0) { LOGF(ERR, "invalid --num-frames: %d\n", o->num_frames); return false; @@ -563,8 +655,8 @@ static void WriteFrames(AVFormatContext *f_context, ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -static bool FindCodec(AVCodec **codec_ptr, - AVOutputFormat *oformat, +static bool FindCodec(const AVCodec **codec_ptr, + const AVOutputFormat *oformat, AVCodecID default_id, const std::string &name, AVMediaType type) { @@ -574,7 +666,7 @@ static bool FindCodec(AVCodec **codec_ptr, return true; } - AVCodec *codec = avcodec_find_encoder_by_name(name.c_str()); + const AVCodec *codec = avcodec_find_encoder_by_name(name.c_str()); if (!codec) { LOGF(ERR, "unknown codec: %s\n", name.c_str()); return false; @@ -598,7 +690,7 @@ static bool FindCodec(AVCodec **codec_ptr, return false; } - AVCodec *codec = avcodec_find_encoder(default_id); + const AVCodec *codec = avcodec_find_encoder(default_id); ASSERT(codec); ASSERT(codec->type == type); @@ -654,7 +746,9 @@ int main(int argc, char *argv[]) { } if (options.dump_formats) { - if (options.verbose) { + if(options.org){ + DumpFormatsOrg(); + }else if (options.verbose) { DumpFormatsVerbose(); } else { DumpFormatsBrief(); @@ -669,8 +763,8 @@ int main(int argc, char *argv[]) { std::vector jpeg_file_names, wav_file_names; { std::vector file_names; - PathGlob(argv[1], [&](const std::string &path, bool is_folder) { - if (is_folder) { + PathGlob(options.folder_name, [&](const std::string &path, bool is_folder) { + if (!is_folder) { file_names.push_back(path); } }); @@ -786,7 +880,7 @@ int main(int argc, char *argv[]) { options.output_file_name.c_str(), AVIO_FLAG_WRITE)); - AVCodec *acodec, *vcodec; + const AVCodec *acodec, *vcodec; if (!FindCodec(&acodec, avf_context->oformat, @@ -929,8 +1023,14 @@ int main(int argc, char *argv[]) { ac_context->bit_rate = 128000; ac_context->sample_rate = (int)wav_fmt->nSamplesPerSec; ac_context->sample_fmt = AV_SAMPLE_FMT_FLTP; +#if LIBAVUTIL_VERSION_MAJOR<58 ac_context->channel_layout = wav_fmt->nChannels == 1 ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO; ac_context->channels = av_get_channel_layout_nb_channels(ac_context->channel_layout); +#else + const AVChannelLayout LAYOUT_MONO=AV_CHANNEL_LAYOUT_MONO; + const AVChannelLayout LAYOUT_STEREO=AV_CHANNEL_LAYOUT_STEREO; + TEST_AV(av_channel_layout_copy(&ac_context->ch_layout,wav_fmt->nChannels==1?&LAYOUT_MONO:&LAYOUT_STEREO)); +#endif ac_context->time_base = av_make_q(1, ac_context->sample_rate); // -strict -2 @@ -953,7 +1053,12 @@ int main(int argc, char *argv[]) { aframe->format = ac_context->sample_fmt; aframe->nb_samples = ac_context->frame_size; aframe->sample_rate = ac_context->sample_rate; + +#if LIBAVUTIL_VERSION_MAJOR<58 aframe->channel_layout = ac_context->channel_layout; +#else + TEST_AV(av_channel_layout_copy(&aframe->ch_layout,&ac_context->ch_layout)); +#endif TEST_AV(av_frame_get_buffer(aframe, 0)); @@ -1147,10 +1252,13 @@ int main(int argc, char *argv[]) { ASSERT(aframe_index <= (size_t)aframe->nb_samples); if (aframe_index == (size_t)aframe->nb_samples) { if (audio_output_file) { - for (int i = 0; - i < aframe->nb_samples; - ++i) { - for (int j = 0; j < aframe->channels; ++j) { + for (int i = 0;i < aframe->nb_samples;++i) { +#if LIBAVCODEC_VERSION_MAJOR<60 + int num_channels=aframe->channels; +#else + int num_channels=aframe->ch_layout.nb_channels; +#endif + for (int j = 0; j < num_channels; ++j) { const float *ch = (float *)aframe->data[j]; fwrite(&ch[i], 4, 1, audio_output_file); @@ -1215,6 +1323,7 @@ int main(int argc, char *argv[]) { avcodec_free_context(&ac_context); avcodec_free_context(&vc_context); +#if LIBAVFORMAT_VERSION_MAJOR==58 // http://stackoverflow.com/questions/43389411/ #ifdef __GNUC__ #pragma GCC diagnostic push @@ -1233,6 +1342,7 @@ int main(int argc, char *argv[]) { } #ifdef __GNUC__ #pragma GCC diagnostic pop +#endif #endif avformat_free_context(avf_context); diff --git a/experimental/test_ffmpeg/test_ffmpeg.inl b/experimental/test_ffmpeg/test_ffmpeg.inl index 1ebfb303..2e08fe2e 100644 --- a/experimental/test_ffmpeg/test_ffmpeg.inl +++ b/experimental/test_ffmpeg/test_ffmpeg.inl @@ -23,12 +23,26 @@ NN(AV_CODEC_FLAG_QSCALE) NN(AV_CODEC_FLAG_4MV) NN(AV_CODEC_FLAG_OUTPUT_CORRUPT) NN(AV_CODEC_FLAG_QPEL) +#ifdef AV_CODEC_FLAG_DROPCHANGED +NN(AV_CODEC_FLAG_DROPCHANGED) +#endif +#ifdef AV_CODEC_FLAG_RECON_FRAME +NN(AV_CODEC_FLAG_RECON_FRAME) +#endif +#ifdef AV_CODEC_FLAG_COPY_OPAQUE +NN(AV_CODEC_FLAG_COPY_OPAQUE) +#endif +#ifdef AV_CODEC_FLAG_FRAME_DURATION +NN(AV_CODEC_FLAG_FRAME_DURATION) +#endif NN(AV_CODEC_FLAG_PASS1) NN(AV_CODEC_FLAG_PASS2) NN(AV_CODEC_FLAG_LOOP_FILTER) NN(AV_CODEC_FLAG_GRAY) NN(AV_CODEC_FLAG_PSNR) +#ifdef AV_CODEC_FLAG_TRUNCATED NN(AV_CODEC_FLAG_TRUNCATED) +#endif NN(AV_CODEC_FLAG_INTERLACED_DCT) NN(AV_CODEC_FLAG_LOW_DELAY) NN(AV_CODEC_FLAG_GLOBAL_HEADER) @@ -47,12 +61,20 @@ NBEGIN(AVCodecFlag2) NN(AV_CODEC_FLAG2_FAST) NN(AV_CODEC_FLAG2_NO_OUTPUT) NN(AV_CODEC_FLAG2_LOCAL_HEADER) +#ifdef AV_CODEC_FLAG2_DROP_FRAME_TIMECODE NN(AV_CODEC_FLAG2_DROP_FRAME_TIMECODE) +#endif NN(AV_CODEC_FLAG2_CHUNKS) NN(AV_CODEC_FLAG2_IGNORE_CROP) NN(AV_CODEC_FLAG2_SHOW_ALL) NN(AV_CODEC_FLAG2_EXPORT_MVS) NN(AV_CODEC_FLAG2_SKIP_MANUAL) +#ifdef AV_CODEC_FLAG2_RO_FLUSH_NOOP +NN(AV_CODEC_FLAG2_RO_FLUSH_NOOP) +#endif +#ifdef AV_CODEC_FLAG2_ICC_PROFILES +NN(AV_CODEC_FLAG2_ICC_PROFILES) +#endif NEND() #undef ENAME @@ -63,7 +85,9 @@ NEND() NBEGIN(AVCodecCap) NN(AV_CODEC_CAP_DRAW_HORIZ_BAND) NN(AV_CODEC_CAP_DR1) +#ifdef AV_CODEC_CAP_TRUNCATED NN(AV_CODEC_CAP_TRUNCATED) +#endif NN(AV_CODEC_CAP_DELAY) NN(AV_CODEC_CAP_SMALL_LAST_FRAME) #ifdef AV_CODEC_CAP_HWACCEL_VDPAU @@ -75,10 +99,37 @@ NN(AV_CODEC_CAP_CHANNEL_CONF) NN(AV_CODEC_CAP_FRAME_THREADS) NN(AV_CODEC_CAP_SLICE_THREADS) NN(AV_CODEC_CAP_PARAM_CHANGE) +#ifdef AV_CODEC_CAP_AUTO_THREADS NN(AV_CODEC_CAP_AUTO_THREADS) +#endif +#ifdef AV_CODEC_CAP_OTHER_THREADS +NN(AV_CODEC_CAP_OTHER_THREADS) +#endif NN(AV_CODEC_CAP_VARIABLE_FRAME_SIZE) +#ifdef AV_CODEC_CAP_AVOID_PROBING +NN(AV_CODEC_CAP_AVOID_PROBING) +#endif +#ifdef AV_CODEC_CAP_HARDWARE +NN(AV_CODEC_CAP_HARDWARE) +#endif +#ifdef AV_CODEC_CAP_INTRA_ONLY NN(AV_CODEC_CAP_INTRA_ONLY) +#endif +#ifdef AV_CODEC_CAP_LOSSLESS NN(AV_CODEC_CAP_LOSSLESS) +#endif +#ifdef AV_CODEC_CAP_HYBRID +NN(AV_CODEC_CAP_HYBRID) +#endif +#ifdef AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE +NN(AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE) +#endif +#ifdef AV_CODEC_CAP_ENCODER_FLUSH +NN(AV_CODEC_CAP_ENCODER_FLUSH) +#endif +#ifdef AV_CODEC_CAP_ENCODER_RECON_FRAME +NN(AV_CODEC_CAP_ENCODER_RECON_FRAME) +#endif NEND() #undef ENAME @@ -112,3 +163,12 @@ NEND() ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// + +#define ENAME int +NBEGIN(AVChannelOrder) +NN(AV_CHANNEL_ORDER_UNSPEC) +NN(AV_CHANNEL_ORDER_NATIVE) +NN(AV_CHANNEL_ORDER_CUSTOM) +NN(AV_CHANNEL_ORDER_AMBISONIC) +NEND() +#undef ENAME From 9255015d731449c19c8bdfebf51604b217cd0918 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sun, 17 Nov 2024 16:42:11 +0000 Subject: [PATCH 19/31] Fix up ffmpeg code in b2 for ffmpeg 7. Intended to cover all versions from 4-7 - to be confirmed. --- doc/Building-on-Unix.md | 22 ++-- doc/b2_notes.org | 16 ++- experimental/test_ffmpeg/test_ffmpeg.cpp | 126 +++++++++++------------ experimental/test_ffmpeg/test_ffmpeg.inl | 4 +- src/b2/CMakeLists.txt | 1 - src/b2/VideoWriterFFmpeg.cpp | 62 +++++++++-- 6 files changed, 142 insertions(+), 89 deletions(-) diff --git a/doc/Building-on-Unix.md b/doc/Building-on-Unix.md index bf07a4c6..349969fc 100644 --- a/doc/Building-on-Unix.md +++ b/doc/Building-on-Unix.md @@ -15,13 +15,14 @@ necessary.) - Xcode -There are additional dependencies for video writing, which are -optional. The project will build without these, but video writing +## Additional prerequisites for video writing + +These are optional. b2 will build without them, but video writing won't be available. -- FFmpeg libs (I used `ffmpeg @4.2.1_2+gpl2` from MacPorts) +- FFmpeg libs (should work with FFmpeg 4 or above) -Video writing on macOS is experimental. +Yuu can get these from MacPorts or Homebrew. # Linux prerequisites @@ -39,18 +40,17 @@ ticked. ## Additional prerequisites for video writing -There are additional dependencies for video writing, which are -optional. The project will build without these, but video writing +These are optional. b2 will build without them, but video writing won't be available. - `libx264-dev` apt package - `FFmpeg` libs: libavcodec 58+, libavformat 58+, libavutil 56+, - libswresample 3+, libswscale 5+ - not sure about the version - numbering here, but this seems to correspond to FFmpeg 4.1. + libswresample 3+, libswscale 5+ (this corresponds to FFmpeg 4 or + later) -Watch out for older package manager versions of FFmpeg! It's easy to -build from [the FFmpeg github repo](https://github.com/FFmpeg/FFmpeg), -though. (I used configure options `--enable-libx264 --enable-gpl +FFmpeg is easy enough to build from the [the FFmpeg github +repo](https://github.com/FFmpeg/FFmpeg) if the package manager version +doesn't suit. (I used configure options `--enable-libx264 --enable-gpl --enable-shared`. I had to re-run `ldconfig` after doing `make install` so the system could find the new libraries.) diff --git a/doc/b2_notes.org b/doc/b2_notes.org index 8651494d..395ae4ac 100644 --- a/doc/b2_notes.org +++ b/doc/b2_notes.org @@ -1397,9 +1397,17 @@ literals. So this can be used to create fodder for the copy function. (The naming is annoyingly wonky.) -** ffmpeg7 +*** Undefined 'av_get_channel_layout_string' +*** No member named 'channel_layouts' in 'AVCodec' -Audio codecs: +FFmpeg5: + + * @deprecated use av_channel_layout_describe() + + +** ffmpeg7 properties + +*** Audio codecs 012v 4xm 8bps a64multi a64multi5 aasc agm aic alias_pix alias_pix amv amv anm ansi apng apng arbc argo asv1 asv1 asv2 asv2 aura aura2 av1 @@ -1438,7 +1446,7 @@ wmv2 wmv2 wmv3 wmv3image wnv1 wrapped_avframe wrapped_avframe xan_wc3 xan_wc4 xbin xbm xbm xface xface xl xpm xwd xwd y41p y41p ylc yop yuv4 yuv4 zerocodec zlib zlib zmbv zmbv -Video codecs: +*** Video codecs 8svx_exp 8svx_fib aac aac aac_at aac_at aac_fixed aac_latm ac3 ac3 ac3_at ac3_fixed ac3_fixed acelp.kelvin adpcm_4xm adpcm_adx adpcm_adx @@ -1485,7 +1493,7 @@ vmdaudio vorbis vorbis wady_dpcm wavarc wavesynth wavpack wavpack wmalossless wmapro wmav1 wmav1 wmav2 wmav2 wmavoice ws_snd1 xan_dpcm xma1 xma2 -Formats: +*** Formats | name | long_name | mime_type | acodec | vcodec | scodec | flags | extensions | |-------------------------+-------------------------------------------------------------+-------------------------------------------+------------------+-----------------+----------+--------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| diff --git a/experimental/test_ffmpeg/test_ffmpeg.cpp b/experimental/test_ffmpeg/test_ffmpeg.cpp index b2d06743..74882bf6 100644 --- a/experimental/test_ffmpeg/test_ffmpeg.cpp +++ b/experimental/test_ffmpeg/test_ffmpeg.cpp @@ -57,9 +57,9 @@ static void PrintLastAVResult(const TestFailArgs *tfa) { //static std::string av_strerror(int result) { // char tmp[AV_ERROR_MAX_STRING_SIZE]; -// +// // av_strerror(result,tmp,sizeof tmp); -// +// // return tmp; //} @@ -102,7 +102,7 @@ LOG_DEFINE(VOUT, "", &log_printer_stdout_and_debugger, false); LOG_DEFINE(OUT, "", &log_printer_stdout_and_debugger); LOG_DEFINE(ERR, "", &log_printer_stderr_and_debugger); -static const LogSet g_log_set={LOG(VOUT),LOG(OUT),LOG(ERR)}; +static const LogSet g_log_set = {LOG(VOUT), LOG(OUT), LOG(ERR)}; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -247,27 +247,27 @@ static bool LoadWAVFile(WAVFile *file, const std::string &file_name) { ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -template -static void PrintFlagsOneLine(T value,const char *(*get_name_fn)(T)) { - bool any=false; - - for(T mask=0;mask!=0;mask<<=1){ - const char *name=(*get_name_fn)(mask); - if(name[0]=='?'){ +template +static void PrintFlagsOneLine(T value, const char *(*get_name_fn)(T)) { + bool any = false; + + for (T mask = 0; mask != 0; mask <<= 1) { + const char *name = (*get_name_fn)(mask); + if (name[0] == '?') { continue; } - - if(value&mask){ - if(any){ - LOGF(OUT," "); + + if (value & mask) { + if (any) { + LOGF(OUT, " "); } - LOG_STR(OUT,name); - any=true; + LOG_STR(OUT, name); + any = true; } } - - if(!any){ - LOG_STR(OUT,"(none)"); + + if (!any) { + LOG_STR(OUT, "(none)"); } } @@ -359,26 +359,26 @@ static void DumpCodecFormats(const T *fmts, } } -#if LIBAVCODEC_VERSION_MAJOR<60 +#if LIBAVCODEC_VERSION_MAJOR < 60 static char *GetChannelLayoutString(char *buf, int n, uint64_t layout) { av_get_channel_layout_string(buf, n, -1, layout); return nullptr; } #endif -#if LIBAVCODEC_VERSION_MAJOR>=60 -static void DumpChLayouts(const AVCodec*c){ - LOGF(OUT,"Channel Layouts: "); +#if LIBAVCODEC_VERSION_MAJOR >= 60 +static void DumpChLayouts(const AVCodec *c) { + LOGF(OUT, "Channel Layouts: "); LOGI(OUT); - - if(!c->ch_layouts){ - LOGF(OUT,"NULL\n"); - }else{ - for(const AVChannelLayout*l=c->ch_layouts;l->order!=0;++l){ - LOGF(OUT, "%zd. ",l-c->ch_layouts); + + if (!c->ch_layouts) { + LOGF(OUT, "NULL\n"); + } else { + for (const AVChannelLayout *l = c->ch_layouts; l->order != 0; ++l) { + LOGF(OUT, "%zd. ", l - c->ch_layouts); LOGI(OUT); - LOGF(OUT,"order=%d (%s)\n",(int)l->order,GetAVChannelOrderEnumName(l->order)); - LOGF(OUT,"nb_channels=%d\n",l->nb_channels); + LOGF(OUT, "order=%d (%s)\n", (int)l->order, GetAVChannelOrderEnumName(l->order)); + LOGF(OUT, "nb_channels=%d\n", l->nb_channels); } } } @@ -414,18 +414,18 @@ static void DumpCodecVerbose(const AVCodec *c) { true, (AVSampleFormat)-1); -#if LIBAVCODEC_VERSION_MAJOR>=60 - +#if LIBAVCODEC_VERSION_MAJOR >= 60 + DumpChLayouts(c); - + #else - + DumpCodecFormats(c->channel_layouts, "Channel Layouts", &GetChannelLayoutString, false, (uint64_t)0); - + #endif DumpCodecFormats(c->supported_samplerates, @@ -460,10 +460,10 @@ static const char *GetAVCodecNameById(AVCodecID id) { return "(none)"; } -static void DumpFormatsOrg(){ +static void DumpFormatsOrg() { void *opaque = nullptr; - LOGF(OUT,"|name|long_name|mime_type|extensions|acodec|vcodec|scodec|flags|\n"); - LOGF(OUT,"|---\n"); + LOGF(OUT, "|name|long_name|mime_type|extensions|acodec|vcodec|scodec|flags|\n"); + LOGF(OUT, "|---\n"); while (const AVOutputFormat *f = av_muxer_iterate(&opaque)) { LOGF(OUT, "|%s ", f->name); LOGF(OUT, "|%s", f->long_name); @@ -472,11 +472,11 @@ static void DumpFormatsOrg(){ LOGF(OUT, "|%s", GetAVCodecNameById(f->audio_codec)); LOGF(OUT, "|%s", GetAVCodecNameById(f->video_codec)); LOGF(OUT, "|%s", GetAVCodecNameById(f->subtitle_codec)); - LOGF(OUT,"|"); - PrintFlagsOneLine(f->flags,&GetAVOutputFormatFlagEnumName); - LOGF(OUT,"\n"); + LOGF(OUT, "|"); + PrintFlagsOneLine(f->flags, &GetAVOutputFormatFlagEnumName); + LOGF(OUT, "\n"); } -} +} static void DumpFormatsVerbose() { void *opaque = nullptr; @@ -535,7 +535,7 @@ struct Options { std::map options; int num_frames = 0; std::string audio_output_file_name; - bool org=false; + bool org = false; }; static bool DoOptions(Options *o, int argc, char *argv[]) { @@ -558,21 +558,21 @@ static bool DoOptions(Options *o, int argc, char *argv[]) { p.AddOption('n', "num-frames").Meta("COUNT").Help("write out max COUNT frames").Arg(&o->num_frames); p.AddOption('A', "audio-output").Meta("FILE").Help("write raw audio data to FILE").Arg(&o->audio_output_file_name); - + p.AddHelpOption(&help); std::vector other_args; - if (!p.Parse(argc, argv, &other_args)||help) { + if (!p.Parse(argc, argv, &other_args) || help) { p.Help(argv[0]); - + // not great to return exit code 1 on -h, but it's only test code return false; } - + if (other_args.size() > 1) { LOGF(ERR, "Must specify exactly one folder\n"); return false; - } else if(other_args.size()==1){ + } else if (other_args.size() == 1) { o->folder_name = other_args[0]; } @@ -746,9 +746,9 @@ int main(int argc, char *argv[]) { } if (options.dump_formats) { - if(options.org){ + if (options.org) { DumpFormatsOrg(); - }else if (options.verbose) { + } else if (options.verbose) { DumpFormatsVerbose(); } else { DumpFormatsBrief(); @@ -1023,13 +1023,13 @@ int main(int argc, char *argv[]) { ac_context->bit_rate = 128000; ac_context->sample_rate = (int)wav_fmt->nSamplesPerSec; ac_context->sample_fmt = AV_SAMPLE_FMT_FLTP; -#if LIBAVUTIL_VERSION_MAJOR<58 +#if LIBAVUTIL_VERSION_MAJOR < 58 ac_context->channel_layout = wav_fmt->nChannels == 1 ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO; ac_context->channels = av_get_channel_layout_nb_channels(ac_context->channel_layout); #else - const AVChannelLayout LAYOUT_MONO=AV_CHANNEL_LAYOUT_MONO; - const AVChannelLayout LAYOUT_STEREO=AV_CHANNEL_LAYOUT_STEREO; - TEST_AV(av_channel_layout_copy(&ac_context->ch_layout,wav_fmt->nChannels==1?&LAYOUT_MONO:&LAYOUT_STEREO)); + const AVChannelLayout LAYOUT_MONO = AV_CHANNEL_LAYOUT_MONO; + const AVChannelLayout LAYOUT_STEREO = AV_CHANNEL_LAYOUT_STEREO; + TEST_AV(av_channel_layout_copy(&ac_context->ch_layout, wav_fmt->nChannels == 1 ? &LAYOUT_MONO : &LAYOUT_STEREO)); #endif ac_context->time_base = av_make_q(1, ac_context->sample_rate); @@ -1053,11 +1053,11 @@ int main(int argc, char *argv[]) { aframe->format = ac_context->sample_fmt; aframe->nb_samples = ac_context->frame_size; aframe->sample_rate = ac_context->sample_rate; - -#if LIBAVUTIL_VERSION_MAJOR<58 + +#if LIBAVUTIL_VERSION_MAJOR < 58 aframe->channel_layout = ac_context->channel_layout; #else - TEST_AV(av_channel_layout_copy(&aframe->ch_layout,&ac_context->ch_layout)); + TEST_AV(av_channel_layout_copy(&aframe->ch_layout, &ac_context->ch_layout)); #endif TEST_AV(av_frame_get_buffer(aframe, 0)); @@ -1252,11 +1252,11 @@ int main(int argc, char *argv[]) { ASSERT(aframe_index <= (size_t)aframe->nb_samples); if (aframe_index == (size_t)aframe->nb_samples) { if (audio_output_file) { - for (int i = 0;i < aframe->nb_samples;++i) { -#if LIBAVCODEC_VERSION_MAJOR<60 - int num_channels=aframe->channels; + for (int i = 0; i < aframe->nb_samples; ++i) { +#if LIBAVCODEC_VERSION_MAJOR < 60 + int num_channels = aframe->channels; #else - int num_channels=aframe->ch_layout.nb_channels; + int num_channels = aframe->ch_layout.nb_channels; #endif for (int j = 0; j < num_channels; ++j) { const float *ch = (float *)aframe->data[j]; @@ -1323,7 +1323,7 @@ int main(int argc, char *argv[]) { avcodec_free_context(&ac_context); avcodec_free_context(&vc_context); -#if LIBAVFORMAT_VERSION_MAJOR==58 +#if LIBAVFORMAT_VERSION_MAJOR == 58 // http://stackoverflow.com/questions/43389411/ #ifdef __GNUC__ #pragma GCC diagnostic push diff --git a/experimental/test_ffmpeg/test_ffmpeg.inl b/experimental/test_ffmpeg/test_ffmpeg.inl index 2e08fe2e..0b8da1cb 100644 --- a/experimental/test_ffmpeg/test_ffmpeg.inl +++ b/experimental/test_ffmpeg/test_ffmpeg.inl @@ -40,7 +40,7 @@ NN(AV_CODEC_FLAG_PASS2) NN(AV_CODEC_FLAG_LOOP_FILTER) NN(AV_CODEC_FLAG_GRAY) NN(AV_CODEC_FLAG_PSNR) -#ifdef AV_CODEC_FLAG_TRUNCATED +#ifdef AV_CODEC_FLAG_TRUNCATED NN(AV_CODEC_FLAG_TRUNCATED) #endif NN(AV_CODEC_FLAG_INTERLACED_DCT) @@ -72,7 +72,7 @@ NN(AV_CODEC_FLAG2_SKIP_MANUAL) #ifdef AV_CODEC_FLAG2_RO_FLUSH_NOOP NN(AV_CODEC_FLAG2_RO_FLUSH_NOOP) #endif -#ifdef AV_CODEC_FLAG2_ICC_PROFILES +#ifdef AV_CODEC_FLAG2_ICC_PROFILES NN(AV_CODEC_FLAG2_ICC_PROFILES) #endif NEND() diff --git a/src/b2/CMakeLists.txt b/src/b2/CMakeLists.txt index 694751b3..8e1a0a1d 100644 --- a/src/b2/CMakeLists.txt +++ b/src/b2/CMakeLists.txt @@ -246,7 +246,6 @@ endif() if(FFMPEG_FOUND) target_sources(b2 PRIVATE VideoWriterFFmpeg.cpp VideoWriterFFmpeg.h) target_link_libraries(b2 PRIVATE ${FFMPEG_LINK_LIBRARIES}) - # target_link_directories(b2 PRIVATE ${FFMPEG_LIBRARY_DIRS}) target_include_directories(b2 PRIVATE ${FFMPEG_INCLUDE_DIRS}) target_compile_definitions(b2 PRIVATE ${FFMPEG_DEFINITIONS} -DHAVE_FFMPEG=1) endif() diff --git a/src/b2/VideoWriterFFmpeg.cpp b/src/b2/VideoWriterFFmpeg.cpp index 67b7655a..2e4cc630 100644 --- a/src/b2/VideoWriterFFmpeg.cpp +++ b/src/b2/VideoWriterFFmpeg.cpp @@ -53,13 +53,17 @@ static std::vector g_formats; static bool g_can_write_video = false; -static AVCodec *g_acodec; +static const AVCodec *g_acodec; static AVSampleFormat g_aformat; -static AVCodec *g_vcodec; +static const AVCodec *g_vcodec; static SDL_AudioSpec g_audio_spec; +#if LIBAVUTIL_VERSION_MAJOR >= 57 +static const AVChannelLayout CHANNEL_LAYOUT_MONO = AV_CHANNEL_LAYOUT_MONO; +#endif + ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -89,6 +93,7 @@ class VideoWriterFFmpeg : public VideoWriter { if (m_ofcontext) { this->CloseFile(); +#if LIBAVFORMAT_VERSION_MAJOR == 58 // http://stackoverflow.com/questions/43389411/ #ifdef __GNUC__ #pragma GCC diagnostic push @@ -108,7 +113,7 @@ class VideoWriterFFmpeg : public VideoWriter { #ifdef __GNUC__ #pragma GCC diagnostic pop #endif - +#endif avformat_free_context(m_ofcontext); m_ofcontext = nullptr; } @@ -194,13 +199,24 @@ class VideoWriterFFmpeg : public VideoWriter { m_acontext->bit_rate = format->abitrate; m_acontext->sample_rate = g_audio_spec.freq; m_acontext->sample_fmt = g_aformat; +#if LIBAVUTIL_VERSION_MAJOR < 57 m_acontext->channel_layout = AV_CH_LAYOUT_MONO; m_acontext->channels = 1; +#else + rc = av_channel_layout_copy(&m_acontext->ch_layout, &CHANNEL_LAYOUT_MONO); + if (rc < 0) { + return this->Error(rc, "av_channel_layout_copy (acontext)"); + } +#endif m_acontext->time_base = av_make_q(1, m_acontext->sample_rate); { char channel_desc[1000]; +#if LIBAVUTIL_VERSION_MAJOR < 57 av_get_channel_layout_string(channel_desc, sizeof channel_desc, m_acontext->channels, m_acontext->channel_layout); +#else + av_channel_layout_describe(&m_acontext->ch_layout, channel_desc, sizeof channel_desc); +#endif LOGF(FFMPEG, "Audio context: "); LOGI(FFMPEG); @@ -229,7 +245,14 @@ class VideoWriterFFmpeg : public VideoWriter { m_aframe->format = m_acontext->sample_fmt; m_aframe->nb_samples = m_acontext->frame_size; m_aframe->sample_rate = m_acontext->sample_rate; +#if LIBAVUTIL_VERSION_MAJOR < 57 m_aframe->channel_layout = m_acontext->channel_layout; +#else + rc = av_channel_layout_copy(&m_aframe->ch_layout, &m_acontext->ch_layout); + if (rc != 0) { + return this->Error(rc, "av_channel_layout_copy (aframe)"); + } +#endif rc = av_frame_get_buffer(m_aframe, 0); if (rc < 0) { @@ -551,17 +574,35 @@ static bool IsSupported(T value, } } -static bool IsChannelLayoutSupported(AVCodec *codec, +#if LIBAVUTIL_VERSION_MAJOR < 57 +static bool IsChannelLayoutSupported(const AVCodec *codec, uint64_t channel_layout) { return IsSupported(channel_layout, codec->channel_layouts, (uint64_t)0); } +#else +static bool IsChannelLayoutSupported(const AVCodec *codec, + const AVChannelLayout *ch_layout) { + if (!codec->ch_layouts) { + // Judging by the encode_audio example, if ch_layouts is NULL, anything goes? + return true; + } + + for (const AVChannelLayout *l = codec->ch_layouts; l->order != 0; ++l) { + if (av_channel_layout_compare(l, ch_layout) == 0) { + return true; + } + } -static bool IsSampleFormatSupported(AVCodec *codec, + return false; +} +#endif + +static bool IsSampleFormatSupported(const AVCodec *codec, AVSampleFormat fmt) { return IsSupported(fmt, codec->sample_fmts, (AVSampleFormat)-1); } -static int FindBestSampleRate(AVCodec *codec, +static int FindBestSampleRate(const AVCodec *codec, int rate) { int best = rate; int64_t best_error = INT64_MAX; @@ -610,8 +651,13 @@ bool InitFFmpeg(Messages *messages) { goto bad; } - if (!IsChannelLayoutSupported(g_acodec, AV_CH_LAYOUT_MONO)) { - messages->e.f("FFmpeg: %s codec doesn't support float audio\n", g_acodec->name); +#if LIBAVUTIL_VERSION_MAJOR < 57 + const bool is_mono_supported = IsChannelLayoutSupported(g_acodec, AV_CH_LAYOUT_MONO); +#else + const bool is_mono_supported = IsChannelLayoutSupported(g_acodec, &CHANNEL_LAYOUT_MONO); +#endif + if (!is_mono_supported) { + messages->e.f("FFmpeg: %s codec doesn't support mono output\n", g_acodec->name); goto bad; } From b5b6016026ad9b3f38779139c66653ddef0c9d01 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sun, 17 Nov 2024 19:44:57 +0000 Subject: [PATCH 20/31] Fix some build issues found on Ubuntu 24. --- src/b2/BeebThread.cpp | 1 + src/b2/JobQueue.cpp | 1 + src/b2/load_save.cpp | 2 +- src/shared/c/file_io.cpp | 2 ++ 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/b2/BeebThread.cpp b/src/b2/BeebThread.cpp index d86ceeb6..e8827f8a 100644 --- a/src/b2/BeebThread.cpp +++ b/src/b2/BeebThread.cpp @@ -32,6 +32,7 @@ #include "BeebLinkHTTPHandler.h" #include #include "profiler.h" +#include #include #include "BeebThread.inl" diff --git a/src/b2/JobQueue.cpp b/src/b2/JobQueue.cpp index d4a0d73b..f569e057 100644 --- a/src/b2/JobQueue.cpp +++ b/src/b2/JobQueue.cpp @@ -1,6 +1,7 @@ #include #include "JobQueue.h" #include +#include ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// diff --git a/src/b2/load_save.cpp b/src/b2/load_save.cpp index 7dd1d4a8..ea4ac670 100644 --- a/src/b2/load_save.cpp +++ b/src/b2/load_save.cpp @@ -215,7 +215,7 @@ static bool FindAssetLinux(std::string *result, size_t size; bool can_write; - if (!GetFileDetails(&size, &can_write, result->c_str())) { + if (!PathIsFileOnDisk(*result, &size, &can_write)) { // Whatever the reason, this path obviously ain't it. LOGF(LOADSAVE, "no.\n"); return false; diff --git a/src/shared/c/file_io.cpp b/src/shared/c/file_io.cpp index 795ddf2d..77545a71 100644 --- a/src/shared/c/file_io.cpp +++ b/src/shared/c/file_io.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include #include From 5b19deae6fdedc8db9e4f59b3ff183481d8162a0 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sun, 17 Nov 2024 22:24:12 +0000 Subject: [PATCH 21/31] Fix cmake FFMPEG_FOUND logging. --- CMakeLists.txt | 47 ++++++++++++++++++----------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1de122dc..e1c6c8b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -269,36 +269,25 @@ if(LINUX OR OSX) find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(FFMPEG libavcodec libavformat libavutil libswresample libswscale) - if(NOT ${FFMPEG_FOUND}) - message(STATUS "FFmpeg not found - video writing not available") + if(FFMPEG_FOUND EQUAL 1) + message(STATUS "FFmpeg found:") + + message(STATUS " FFMPEG_LIBRARIES: ${FFMPEG_LIBRARIES}") + message(STATUS " FFMPEG_LINK_LIBRARIES: ${FFMPEG_LINK_LIBRARIES}") + message(STATUS " FFMPEG_LIBRARY_DIRS: ${FFMPEG_LIBRARY_DIRS}") + message(STATUS " FFMPEG_LDFLAGS: ${FFMPEG_LDFLAGS}") + message(STATUS " FFMPEG_LDFLAGS_OTHER: ${FFMPEG_LDFLAGS_OTHER}") + message(STATUS " FFMPEG_INCLUDE_DIRS: ${FFMPEG_INCLUDE_DIRS}") + message(STATUS " FFMPEG_CFLAGS: ${FFMPEG_CFLAGS}") + message(STATUS " FFMPEG_CFLAGS_OTHER: ${FFMPEG_CFLAGS_OTHER}") + message(STATUS "FFmpeg library versions:") + message(STATUS " libavcodec version: ${FFMPEG_libavcodec_VERSION}") + message(STATUS " libavformat version: ${FFMPEG_libavformat_VERSION}") + message(STATUS " libavutil version: ${FFMPEG_libavutil_VERSION}") + message(STATUS " libswresample version: ${FFMPEG_libswresample_VERSION}") + message(STATUS " libswscale version: ${FFMPEG_libswscale_VERSION}") else() - set(FFMPEG__GOOD ON) - - if(FFMPEG__GOOD) - message(STATUS "FFmpeg found - ") - - message(STATUS " FFMPEG_LIBRARIES: ${FFMPEG_LIBRARIES}") - message(STATUS " FFMPEG_LINK_LIBRARIES: ${FFMPEG_LINK_LIBRARIES}") - message(STATUS " FFMPEG_LIBRARY_DIRS: ${FFMPEG_LIBRARY_DIRS}") - message(STATUS " FFMPEG_LDFLAGS: ${FFMPEG_LDFLAGS}") - message(STATUS " FFMPEG_LDFLAGS_OTHER: ${FFMPEG_LDFLAGS_OTHER}") - message(STATUS " FFMPEG_INCLUDE_DIRS: ${FFMPEG_INCLUDE_DIRS}") - message(STATUS " FFMPEG_CFLAGS: ${FFMPEG_CFLAGS}") - message(STATUS " FFMPEG_CFLAGS_OTHER: ${FFMPEG_CFLAGS_OTHER}") - - # message(STATUS " Libraries: ${FFMPEG_LIBRARIES}") - # message(STATUS " Library dirs: ${FFMPEG_LIBRARY_DIRS}") - # message(STATUS " Include dirs: ${FFMPEG_INCLUDE_DIRS}") - # message(STATUS " Definitions: ${FFMPEG_DEFINITIONS}") - message(STATUS " libavcodec version: ${FFMPEG_libavcodec_VERSION}") - message(STATUS " libavformat version: ${FFMPEG_libavformat_VERSION}") - message(STATUS " libavutil version: ${FFMPEG_libavutil_VERSION}") - message(STATUS " libswresample version: ${FFMPEG_libswresample_VERSION}") - message(STATUS " libswscale version: ${FFMPEG_libswscale_VERSION}") - else() - message(WARNING "FFmpeg not found - video writing not available") - set(FFMPEG_FOUND OFF) - endif() + message(WARNING "FFmpeg not found. Video writing will not be available") endif() endif() endif() From 1362cbd649ba5419045f26bd156d6a6a89814e09 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sun, 17 Nov 2024 22:24:49 +0000 Subject: [PATCH 22/31] Fix some ffmpeg version stuff. --- experimental/test_ffmpeg/test_ffmpeg.inl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/experimental/test_ffmpeg/test_ffmpeg.inl b/experimental/test_ffmpeg/test_ffmpeg.inl index 0b8da1cb..d9ace3ba 100644 --- a/experimental/test_ffmpeg/test_ffmpeg.inl +++ b/experimental/test_ffmpeg/test_ffmpeg.inl @@ -99,7 +99,7 @@ NN(AV_CODEC_CAP_CHANNEL_CONF) NN(AV_CODEC_CAP_FRAME_THREADS) NN(AV_CODEC_CAP_SLICE_THREADS) NN(AV_CODEC_CAP_PARAM_CHANGE) -#ifdef AV_CODEC_CAP_AUTO_THREADS +#if defined AV_CODEC_CAP_AUTO_THREADS && !defined AV_CODEC_CAP_OTHER_THREADS NN(AV_CODEC_CAP_AUTO_THREADS) #endif #ifdef AV_CODEC_CAP_OTHER_THREADS @@ -164,6 +164,7 @@ NEND() ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// +#if LIBAVFORMAT_VERSION_MAJOR>=59 #define ENAME int NBEGIN(AVChannelOrder) NN(AV_CHANNEL_ORDER_UNSPEC) @@ -172,3 +173,4 @@ NN(AV_CHANNEL_ORDER_CUSTOM) NN(AV_CHANNEL_ORDER_AMBISONIC) NEND() #undef ENAME +#endif From 90e58a87fd76310cb1c32acbb947ddd125fb08cd Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sun, 17 Nov 2024 22:25:52 +0000 Subject: [PATCH 23/31] Use 65535 as frame duration denominator. Avoid ffmpeg warning when producing mp4 output. --- experimental/test_ffmpeg/test_ffmpeg.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/experimental/test_ffmpeg/test_ffmpeg.cpp b/experimental/test_ffmpeg/test_ffmpeg.cpp index 74882bf6..0d9d1340 100644 --- a/experimental/test_ffmpeg/test_ffmpeg.cpp +++ b/experimental/test_ffmpeg/test_ffmpeg.cpp @@ -827,8 +827,12 @@ int main(int argc, char *argv[]) { fps = 30.; } - frame_duration.num = 100000; - frame_duration.den = (int)(fps * 100000.); + // Ensure denominator is max 65535. See mpegvideo_enc.c. + // frame_duration.num = 100000; + // frame_duration.den = (int)(fps * 100000.); + frame_duration.num=(int)(65535.0/fps); + frame_duration.den=65535; + // the calculation can be (# jpegs)*(bytes/sec)/(bytes data), // which could be used as the MF_MT_FRAME_RATE_FPS ratio. But // they're only UINT32s and that doesn't leave much overhead. From df43ad409efbda0bfcf9e57577a8bd9f32df42b4 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sun, 17 Nov 2024 22:27:21 +0000 Subject: [PATCH 24/31] Comment out assert that was tripping in one of my tests. Need to fix this later... --- experimental/test_ffmpeg/test_ffmpeg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experimental/test_ffmpeg/test_ffmpeg.cpp b/experimental/test_ffmpeg/test_ffmpeg.cpp index 0d9d1340..6167ca0f 100644 --- a/experimental/test_ffmpeg/test_ffmpeg.cpp +++ b/experimental/test_ffmpeg/test_ffmpeg.cpp @@ -1205,7 +1205,7 @@ int main(int argc, char *argv[]) { size_t num_samples = (limit - error) / step; ASSERT(ac_context->frame_size >= 0); - TEST_EQ_UU(wav_fmt->nChannels, 2u); + //TEST_EQ_UU(wav_fmt->nChannels, 2u); TEST_EQ_UU(wav_fmt->wBitsPerSample, 16u); for (size_t sample_idx = 0; sample_idx < num_samples; From ec8768fca8fddfc7bfcb53d02620d0c6e964aa30 Mon Sep 17 00:00:00 2001 From: Tom Seddon <-> Date: Sun, 17 Nov 2024 22:32:57 +0000 Subject: [PATCH 25/31] Tweak docs. --- doc/Building-on-Unix.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/doc/Building-on-Unix.md b/doc/Building-on-Unix.md index 349969fc..2d6cc512 100644 --- a/doc/Building-on-Unix.md +++ b/doc/Building-on-Unix.md @@ -7,10 +7,16 @@ I do some of the development on macOS, so that version should work well. The Linux version doesn't get much testing by me, though I do try it -on Ubuntu 18.04 occasionally. But I've had few reports of problems. +on a Ubuntu VM occasionally. But I've had few reports of problems. (Please [create an issue](https://github.com/tom-seddon/b2/issues) if necessary.) +# Common prerequisites + +- [cmake](https://cmake.org/) version 3.20+ on the PATH (I built it from [the CMake github repo](https://github.com/Kitware/CMake)) +- [ninja](https://ninja-build.org/) (build from the [Ninja github repo](https://github.com/ninja-build/ninja), install from MacPorts, etc.) +- Python 3.x + # macOS prerequisites - Xcode @@ -50,15 +56,7 @@ won't be available. FFmpeg is easy enough to build from the [the FFmpeg github repo](https://github.com/FFmpeg/FFmpeg) if the package manager version -doesn't suit. (I used configure options `--enable-libx264 --enable-gpl ---enable-shared`. I had to re-run `ldconfig` after doing `make -install` so the system could find the new libraries.) - -# Common prerequisites - -- [cmake](https://cmake.org/) version 3.20+ on the PATH (I built it from [the CMake github repo](https://github.com/Kitware/CMake)) -- [ninja](https://ninja-build.org/) (build from the [Ninja github repo](https://github.com/ninja-build/ninja), install from MacPorts, etc.) -- Python 3.x +doesn't suit. # Building From 29434a2dcb88f95e17ea1107c4840df552a30b26 Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Sun, 17 Nov 2024 22:50:47 +0000 Subject: [PATCH 26/31] Fix Gtk file chooser type when saving. See #387. This is not a fix, but it'll do for now. --- src/b2/native_ui_gtk.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/b2/native_ui_gtk.cpp b/src/b2/native_ui_gtk.cpp index 55f0d036..e2feafe8 100644 --- a/src/b2/native_ui_gtk.cpp +++ b/src/b2/native_ui_gtk.cpp @@ -189,8 +189,8 @@ std::string OpenFileDialogGTK(const std::vector &filters std::string SaveFileDialogGTK(const std::vector &filters, const std::string &default_path) { - GtkWidget *gdialog = CreateFileDialog("Open File", - GTK_FILE_CHOOSER_ACTION_OPEN); + GtkWidget *gdialog = CreateFileDialog("Save File", + GTK_FILE_CHOOSER_ACTION_SAVE); AddFilters(gdialog, filters); SetDefaultPath(gdialog, default_path); From 9aa29c841f4dea37c77eeaaac3801db244735e0d Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Mon, 18 Nov 2024 12:41:15 +0000 Subject: [PATCH 27/31] Install latest ffmpeg on macOS CI. --- Makefile.osx.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.osx.mak b/Makefile.osx.mak index 9ea4d9ee..b6cb531d 100644 --- a/Makefile.osx.mak +++ b/Makefile.osx.mak @@ -45,7 +45,7 @@ github_ci_macos_homebrew: .PHONY:github_ci_macos_homebrew_ffmpeg github_ci_macos_homebrew_ffmpeg: - brew install ffmpeg@4 + brew install ffmpeg .PHONY:_github_ci_macos_release _github_ci_macos_release: From 359af1a31def15b286676ee45f06223495ce02af Mon Sep 17 00:00:00 2001 From: Tom Seddon Date: Mon, 18 Nov 2024 18:11:58 +0000 Subject: [PATCH 28/31] Set up separate Linux CI builds for with/without ffmpeg. --- .github/workflows/b2.yml | 12 ++++++++++-- Makefile.unix.mak | 20 +++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/.github/workflows/b2.yml b/.github/workflows/b2.yml index 3a84879c..486d07ce 100644 --- a/.github/workflows/b2.yml +++ b/.github/workflows/b2.yml @@ -7,13 +7,21 @@ on: - wip/master jobs: - build-linux: + build-linux-with-ffmpeg: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: recursive - - run: make github_ci_ubuntu + - run: make github_ci_ubuntu_with_ffmpeg + + build-linux-without-ffmpeg: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - run: make github_ci_ubuntu_without_ffmpeg build-windows: runs-on: windows-2019 diff --git a/Makefile.unix.mak b/Makefile.unix.mak index c80c7517..8c186926 100644 --- a/Makefile.unix.mak +++ b/Makefile.unix.mak @@ -122,8 +122,22 @@ travis_ci_before_install_linux: ########################################################################## ########################################################################## -.PHONY:github_ci_ubuntu -github_ci_ubuntu: +.PHONY:github_ci_ubuntu_without_ffmpeg +github_ci_ubuntu_without_ffmpeg: + $(MAKE) _github_ci_ubuntu_start + $(MAKE) _github_ci_ubuntu_release SUFFIX1=noffmpeg- + +.PHONY:github_ci_ubuntu_with_ffmpeg +github_cu_ubuntu_with_ffmpeg: + $(MAKE) _github_ci_ubuntu_start + sudo apt-get -y install ffmpeg libavcodec-dev libavutil-dev libswresample-dev libavformat-dev libswscale-dev + $(MAKE) _github_ci_ubuntu_release SUFFIX1=ffmpeg- + +.PHONY:_github_ci_ubuntu_start +_github_ci_ubuntu_start: sudo apt-get -y update sudo apt-get -y install libcurl4-openssl-dev libgl1-mesa-dev libglvnd-dev libgtk2.0-dev libpulse-dev uuid-dev libsdl2-dev libuv1-dev ninja-build - $(PYTHON3) "./etc/release/release.py" --verbose $(shell $(PYTHON3) "./etc/release/release2.py" print-suffix) + +.PHONY:_github_ci_ubuntu_release +_github_ci_ubuntu_release: + $(PYTHON3) "./etc/release/release.py" --verbose $(SUFFIX1)$(shell $(PYTHON3) "./etc/release/release2.py" print-suffix) From 2ed15a41eafccc464b9cc2db3df1abfa775ea90d Mon Sep 17 00:00:00 2001 From: Tom Seddon <-> Date: Mon, 18 Nov 2024 18:24:52 +0000 Subject: [PATCH 29/31] Fix Makefile typo. --- Makefile.unix.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.unix.mak b/Makefile.unix.mak index 8c186926..62f7c82b 100644 --- a/Makefile.unix.mak +++ b/Makefile.unix.mak @@ -128,7 +128,7 @@ github_ci_ubuntu_without_ffmpeg: $(MAKE) _github_ci_ubuntu_release SUFFIX1=noffmpeg- .PHONY:github_ci_ubuntu_with_ffmpeg -github_cu_ubuntu_with_ffmpeg: +github_ci_ubuntu_with_ffmpeg: $(MAKE) _github_ci_ubuntu_start sudo apt-get -y install ffmpeg libavcodec-dev libavutil-dev libswresample-dev libavformat-dev libswscale-dev $(MAKE) _github_ci_ubuntu_release SUFFIX1=ffmpeg- From 5c02457e106e99824c5c514a4d2d01426b4dcde4 Mon Sep 17 00:00:00 2001 From: zoltanvb Date: Wed, 20 Nov 2024 08:10:23 +0100 Subject: [PATCH 30/31] Changes to get it compiling (WIP, does not work yet) --- src/libretro/Makefile.common | 8 ++++++-- src/libretro/adapters.cpp | 8 ++++++++ src/libretro/adapters.h | 15 +++++++++++++++ src/libretro/core.cpp | 8 +++++--- src/libretro/core.h | 8 ++++---- 5 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/libretro/Makefile.common b/src/libretro/Makefile.common index 9ae7837f..eb520866 100644 --- a/src/libretro/Makefile.common +++ b/src/libretro/Makefile.common @@ -44,8 +44,9 @@ SOURCES_CPP := \ $(CORE_DIR)/shared/c/system.cpp \ $(CORE_DIR)/shared/c/mutex.cpp \ $(CORE_DIR)/shared/c/sha1.cpp \ - $(CORE_DIR)/b2/DiscGeometry.cpp \ - $(CORE_DIR)/b2/DirectDiscImage.cpp \ + $(CORE_DIR)/shared/c/file_io.cpp \ + $(CORE_DIR)/beeb/src/DiscGeometry.cpp \ + $(CORE_DIR)/beeb/src/DirectDiscImage.cpp \ $(CORE_DIR)/b2/filters.cpp \ $(CORE_DIR)/b2/Remapper.cpp \ @@ -60,6 +61,7 @@ SOURCES_CPP += \ SOURCES_CPP += \ $(CORE_DIR)/shared/c/system_posix.cpp \ + $(CORE_DIR)/shared/c/path_posix.cpp \ else ifneq (,$(findstring unix,$(platform))) SOURCES_CPP += \ @@ -67,10 +69,12 @@ SOURCES_CPP += \ SOURCES_CPP += \ $(CORE_DIR)/shared/c/system_posix.cpp \ + $(CORE_DIR)/shared/c/path_posix.cpp \ else ifneq (,$(findstring win,$(platform))) SOURCES_CPP += \ $(CORE_DIR)/shared/c/system_windows.cpp \ + $(CORE_DIR)/shared/c/path_windows.cpp \ endif diff --git a/src/libretro/adapters.cpp b/src/libretro/adapters.cpp index 72e44b68..093ba079 100644 --- a/src/libretro/adapters.cpp +++ b/src/libretro/adapters.cpp @@ -115,3 +115,11 @@ void FileDialog::AddFilter(std::string title, std::vector patterns) (void)title; (void)patterns; } + +LibretroMessages::LibretroMessages() +: LogSet{m_info, m_warning, m_error} + , m_info("", &log_printer_nowhere, false) + , m_warning("", &log_printer_nowhere, false) + , m_error("", &log_printer_nowhere, false) +{ +} diff --git a/src/libretro/adapters.h b/src/libretro/adapters.h index dd2799bc..c0e730ec 100644 --- a/src/libretro/adapters.h +++ b/src/libretro/adapters.h @@ -4,6 +4,8 @@ #include #include #include "../b2/Messages.h" +#include "../shared/h/shared/file_io.h" +#include "../shared/h/shared/log.h" #ifdef _WIN32 const char PATH_SEPARATOR = '\\'; @@ -24,4 +26,17 @@ bool SaveFile(const std::vector &data, const std::string &path, const L class FileDialog; +class LibretroMessages : public LogSet { + public: + // When default-constructed, all three logs are disabled and go to + // the nowhere printer. + LibretroMessages(); + + + protected: + private: + Log m_info, m_warning, m_error; + +}; + #endif \ No newline at end of file diff --git a/src/libretro/core.cpp b/src/libretro/core.cpp index 44fa9708..af3494ef 100644 --- a/src/libretro/core.cpp +++ b/src/libretro/core.cpp @@ -95,7 +95,7 @@ other QoL #include "../beeb/include/beeb/video.h" #include "../beeb/include/beeb/TVOutput.h" #include "../beeb/include/beeb/OutputData.h" -#include "../b2/DirectDiscImage.h" +#include "../beeb/include/beeb/DirectDiscImage.h" #include "../b2/filters.h" #include "../shared/h/shared/path.h" #include "roms.hpp" @@ -183,6 +183,8 @@ static const char bbcMicroType[50] = {0}; static int model_index = 0; static const ROMType DEFAULT_ROM_TYPES[16] = {}; +LibretroMessages logObject; + static void fallback_log(enum retro_log_level level, const char *fmt, ...) { (void)level; @@ -389,7 +391,7 @@ static bool set_image_index_cb(unsigned index) { } else { diskIndex = index; if (core) { - core->SetDiscImage(0, DirectDiscImage::CreateForFile(diskPaths[diskIndex], nullptr)); + core->SetDiscImage(0, DirectDiscImage::CreateForFile(diskPaths[diskIndex], logObject)); } } return true; @@ -1173,7 +1175,7 @@ bool retro_load_game(const struct retro_game_info *info) create_core(&core); std::string path = info->path; - core->SetDiscImage(0, DirectDiscImage::CreateForFile(path, nullptr)); + core->SetDiscImage(0, DirectDiscImage::CreateForFile(path, logObject)); // Autoboot: if there is [SOMETHING] notice in the filename, use it // otherwise press Shift and hope for autoboot diff --git a/src/libretro/core.h b/src/libretro/core.h index a0ca5dac..414f6eb3 100644 --- a/src/libretro/core.h +++ b/src/libretro/core.h @@ -104,7 +104,7 @@ struct machine_type { const char * name; const std::array * os_standard_rom; const BBCMicroTypeID type; - const DiscInterface * disc_interface; + const DiscInterface* disc_interface; const std::array * rom_array[16]; bool ext_mem; bool beeblink; @@ -120,7 +120,7 @@ machine_type machine_types[] = { "B/Acorn 1770", &OS12_ROM, BBCMicroTypeID_B, - DISC_INTERFACE_ACORN_1770, + &DISC_INTERFACE_ACORN_1770, { nullptr, nullptr, @@ -306,7 +306,7 @@ machine_type machine_types[] = { "B+", &BPlusMOS_ROM, BBCMicroTypeID_BPlus, - DISC_INTERFACE_ACORN_1770, + &DISC_INTERFACE_ACORN_1770, { nullptr, nullptr, @@ -337,7 +337,7 @@ machine_type machine_types[] = { "B+128", &BPlusMOS_ROM, BBCMicroTypeID_BPlus, - DISC_INTERFACE_ACORN_1770, + &DISC_INTERFACE_ACORN_1770, { &writeable_ROM, &writeable_ROM, From 1856ec5d3f07d92b2823bd415b38920e70295fbb Mon Sep 17 00:00:00 2001 From: zoltanvb Date: Wed, 20 Nov 2024 21:01:32 +0100 Subject: [PATCH 31/31] Dummy logprinter. --- src/libretro/adapters.cpp | 10 +++++++--- src/libretro/adapters.h | 14 +++++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/libretro/adapters.cpp b/src/libretro/adapters.cpp index 093ba079..d10ff3c9 100644 --- a/src/libretro/adapters.cpp +++ b/src/libretro/adapters.cpp @@ -115,11 +115,15 @@ void FileDialog::AddFilter(std::string title, std::vector patterns) (void)title; (void)patterns; } +LogPrinterLibretro::LogPrinterLibretro(bool dummy) {} +void LogPrinterLibretro::Print(const char *str, size_t str_len) {} + +LogPrinterLibretro lplibretro(false); LibretroMessages::LibretroMessages() : LogSet{m_info, m_warning, m_error} - , m_info("", &log_printer_nowhere, false) - , m_warning("", &log_printer_nowhere, false) - , m_error("", &log_printer_nowhere, false) + , m_info("", (LogPrinter*)&lplibretro, false) + , m_warning("", (LogPrinter*)&lplibretro, false) + , m_error("", (LogPrinter*)&lplibretro, false) { } diff --git a/src/libretro/adapters.h b/src/libretro/adapters.h index c0e730ec..333b6c50 100644 --- a/src/libretro/adapters.h +++ b/src/libretro/adapters.h @@ -26,13 +26,21 @@ bool SaveFile(const std::vector &data, const std::string &path, const L class FileDialog; +class LogPrinterLibretro : public LogPrinter { + public: + void Print(const char *str, size_t str_len) override; + LogPrinterLibretro(bool dummy); + + protected: + private: +}; + +extern LogPrinterLibretro lplibretro; + class LibretroMessages : public LogSet { public: - // When default-constructed, all three logs are disabled and go to - // the nowhere printer. LibretroMessages(); - protected: private: Log m_info, m_warning, m_error;