diff --git a/.gitignore b/.gitignore index 6f31401..6b6cae9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build/ .vscode/ +bin/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 34aaa99..f049507 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ endif() include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include") set(DETOURS_LIB "${CMAKE_CURRENT_SOURCE_DIR}/lib/detours.lib") +set(PLAYER_LIB "${CMAKE_CURRENT_SOURCE_DIR}/lib/player.lib") set(ENABLE_ICONV OFF CACHE BOOL "Libiconv is not needed.") add_subdirectory(utils) @@ -39,6 +40,7 @@ add_subdirectory("libzip") add_library(jewena_patch SHARED dllmain.cpp config.hpp config.cpp vfs.hpp vfs.cpp string_replace_file.hpp string_replace_file.cpp) target_link_libraries(jewena_patch "${DETOURS_LIB}") +target_link_libraries(jewena_patch "${PLAYER_LIB}") target_link_libraries(jewena_patch utils) target_link_libraries(jewena_patch zip) diff --git a/config.cpp b/config.cpp index fc84d6b..809d099 100644 --- a/config.cpp +++ b/config.cpp @@ -2,6 +2,8 @@ #include "fileop.h" #include "file_reader.h" #include "malloc.h" +#include "str_util.h" +#include "player.h" #include #if _WIN32 @@ -46,3 +48,38 @@ bool Config::Load(std::string path) { fileop::fclose(f); return true; } + +bool Config::IsAppendLogging() { + auto re = configs.find("appendLogging"); + if (re == configs.end()) { + return false; + } + return str_util::parse_bool((*re).second); +} + +int Config::LoggingLevel() { + auto re = configs.find("loggingLevel"); + if (re == configs.end()) { + return AV_LOG_INFO; + } + std::string level = str_util::tolower((*re).second); + if (level == "debug") { + return AV_LOG_DEBUG; + } else if (level == "verbose") { + return AV_LOG_VERBOSE; + } else if (level == "info") { + return AV_LOG_INFO; + } else if (level == "warning") { + return AV_LOG_WARNING; + } else if (level == "error") { + return AV_LOG_ERROR; + } else if (level == "fatal") { + return AV_LOG_FATAL; + } else if (level == "trace") { + return AV_LOG_TRACE; + } else if (level == "panic") { + return AV_LOG_PANIC; + } else { + return AV_LOG_INFO; + } +} diff --git a/config.hpp b/config.hpp index fd11bec..d9b9b77 100644 --- a/config.hpp +++ b/config.hpp @@ -7,6 +7,11 @@ class Config { Config() { configs["defaultFont"] = "微软雅黑"; configs["stringReplaceFile"] = ""; + configs["appendLogging"] = "false"; + configs["loggingFile"] = ""; + configs["loggingLevel"] = "info"; } bool Load(std::string path); + bool IsAppendLogging(); + int LoggingLevel(); }; diff --git a/dllmain.cpp b/dllmain.cpp index 8f15459..5a9de45 100644 --- a/dllmain.cpp +++ b/dllmain.cpp @@ -8,6 +8,7 @@ #include "fileop.h" #include #include "string_replace_file.hpp" +#include "player.h" static HFONT(WINAPI *TrueCreateFontW)(int nHeight, int nWidth, int nEscapement, int nOrientation, int fnWeight, DWORD dwItalic, DWORD dwUnderline, DWORD dwStrikeOut, DWORD dwCharSet, DWORD dwOutPrecision, DWORD dwClipPrecision, DWORD dwQuality, DWORD dwPitchAndFamily, LPCWSTR lpFaceName) = CreateFontW; static HFONT(WINAPI *TrueCreateFontA)(int nHeight, int nWidth, int nEscapement, int nOrientation, int fnWeight, DWORD dwItalic, DWORD dwUnderline, DWORD dwStrikeOut, DWORD dwCharSet, DWORD dwOutPrecision, DWORD dwClipPrecision, DWORD dwQuality, DWORD dwPitchAndFamily, LPCSTR lpFaceName) = CreateFontA; @@ -22,10 +23,21 @@ static decltype(GetFileType) *TrueGetFileType = GetFileType; static decltype(GetFileAttributesW) *TrueGetFileAttributesW = GetFileAttributesW; static decltype(GetFileAttributesExW) *TrueGetFileAttributesExW = GetFileAttributesExW; +typedef int64_t(*OpenMediaFileAndGetDuration)(DWORD* duration, const char* arcName, const char* videoName); +typedef int64_t(*IsPlaying)(); +typedef int64_t(*IsMediaPlaying)(); +typedef int64_t(*ReleaseDirectShowGraph)(); + static Config config; static std::wstring defaultFont; static VFS vfs; static StringReplaceFile replaceFile; +static PlayerSession* player = NULL; +static PlayerSettings* settings = NULL; +static OpenMediaFileAndGetDuration OpenMediaFileAndGetDurationFunc = NULL; +static IsPlaying IsPlayingFunc = NULL; +static IsMediaPlaying IsMediaPlayingFunc = NULL; +static ReleaseDirectShowGraph ReleaseDirectShowGraphFunc = NULL; char* to_utf8(char* target, const char* source, UINT cp) { int count = MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, source, -1, NULL, 0); @@ -73,8 +85,108 @@ PVOID GetHandle() { return (char*)hModule + 0xf40e0; } +HWND* GetHwndPointer() { + HMODULE hModule = GetModuleHandleA(NULL); + return (HWND*)((char*)hModule + 0x1e1620); +} + +OpenMediaFileAndGetDuration GetOpenMediaFileAndGetDuration() { + HMODULE hModule = GetModuleHandleA(NULL); + return (OpenMediaFileAndGetDuration)((char*)hModule + 0xed3d0); +} + +IsPlaying GetIsPlaying() { + HMODULE hModule = GetModuleHandleA(NULL); + return (IsPlaying)((char*)hModule + 0xed810); +} + +IsMediaPlaying GetIsMediaPlaying() { + HMODULE hModule = GetModuleHandleA(NULL); + return (IsMediaPlaying)((char*)hModule + 0xed2f0); +} + +ReleaseDirectShowGraph GetReleaseDirectShowGraph() { + HMODULE hModule = GetModuleHandleA(NULL); + return (ReleaseDirectShowGraph)((char*)hModule + 0xed610); +} + static PVOID h = nullptr; +int64_t HookedOpenMediaFileAndGetDuration(DWORD* duration, const char* arcName, const char* videoName) { + player_free(&player); + player_log(AV_LOG_INFO, "BGI: Open Video: %s, %s\n", arcName, videoName); + int64_t ok = 0; + if (fileop::exists(videoName)) { + player_log(AV_LOG_INFO, "Video file exists: %s\n", videoName); + if (!settings) { + settings = player_settings_init(); + if (!settings) { + player_log(AV_LOG_ERROR, "Failed to initialize player settings.\n"); + goto end; + } + } + player_settings_set_hWnd(settings, (void**)GetHwndPointer()); + if (player_create2(videoName, &player, settings)) { + player_log(AV_LOG_ERROR, "Failed to create player session.\n"); + goto end; + } + if (wait_player_inited(player)) { + player_log(AV_LOG_ERROR, "Failed to initialize player.\n"); + goto end; + } + if (player_play(player)) { + player_log(AV_LOG_ERROR, "Failed to play video.\n"); + goto end; + } + int64_t dur; + if (!player_get_duration(player, &dur)) { + if (duration) { + *duration = dur / 1000; + } + } else { + player_log(AV_LOG_WARNING, "Failed to get video duration.\n"); + } + goto works; + } +end: + ok = OpenMediaFileAndGetDurationFunc(duration, arcName, videoName); +works: + if (duration) { + char tmp[32]; + int64_t dur = *duration * 1000; + player_ts_make_string(tmp, dur); + player_log(AV_LOG_INFO, "Video duration: %s\n", tmp); + } + return ok; +} + +int64_t HookedIsPlaying() { + if (player) { + return player_is_playing(player); + } + return IsPlayingFunc(); +} + +int64_t HookedIsMediaPlaying() { + if (player) { + int64_t re = player_is_playing(player); + // 释放播放器,BGI在播放完毕后不会手动释放 + if (!re) { + player_log(AV_LOG_INFO, "Auto Close\n"); + player_free(&player); + } + return re; + } + int64_t re = IsMediaPlayingFunc(); + return re; +} + +int64_t HookedReleaseDirectShowGraph() { + player_log(AV_LOG_INFO, "BGI: Close\n"); + player_free(&player); + return ReleaseDirectShowGraphFunc(); +} + HFONT WINAPI HookedCreateFontW(int nHeight, int nWidth, int nEscapement, int nOrientation, int fnWeight, DWORD dwItalic, DWORD dwUnderline, DWORD dwStrikeOut, DWORD dwCharSet, DWORD dwOutPrecision, DWORD dwClipPrecision, DWORD dwQuality, DWORD dwPitchAndFamily, LPCWSTR lpFaceName) { std::wstring name(lpFaceName); if (name == L"Meiryo") { @@ -183,11 +295,24 @@ extern "C" __declspec(dllexport) void Attach() { if (defaultFont.empty()) { defaultFont = L"微软雅黑"; } - vfs.AddArchiveWithErrorMsg("jewena-chs.dat"); + auto loggingFile = config.configs["loggingFile"]; + if (!loggingFile.empty()) { + set_player_log_file(loggingFile.c_str(), config.IsAppendLogging() ? 1 : 0, config.LoggingLevel()); + } + vfs.AddArchive("jewena-chs.dat"); + vfs.AddArchive("video.dat"); DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); h = GetHandle(); DetourAttach(&h, (PVOID)jis_to_utf8); + OpenMediaFileAndGetDurationFunc = GetOpenMediaFileAndGetDuration(); + DetourAttach(&OpenMediaFileAndGetDurationFunc, HookedOpenMediaFileAndGetDuration); + IsPlayingFunc = GetIsPlaying(); + DetourAttach(&IsPlayingFunc, HookedIsPlaying); + IsMediaPlayingFunc = GetIsMediaPlaying(); + DetourAttach(&IsMediaPlayingFunc, HookedIsMediaPlaying); + ReleaseDirectShowGraphFunc = GetReleaseDirectShowGraph(); + DetourAttach(&ReleaseDirectShowGraphFunc, HookedReleaseDirectShowGraph); DetourAttach(&TrueCreateFontW, HookedCreateFontW); DetourAttach(&TrueCreateFontA, HookedCreateFontA); DetourAttach(&TrueCreateFileW, HookedCreateFileW); diff --git a/include/player.h b/include/player.h new file mode 100644 index 0000000..f34a979 --- /dev/null +++ b/include/player.h @@ -0,0 +1,170 @@ +#ifndef _PLAYER_H +#define _PLAYER_H +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#if _WIN32 +#if BUILD_PLAYER +#define PLAYER_API __declspec(dllexport) +#else +#define PLAYER_API __declspec(dllimport) +#endif +#elif defined(__GNUC__) +#define PLAYER_API __attribute__((visibility("default"))) +#else +#define PLAYER_API +#endif + +typedef struct PlayerSession PlayerSession; +typedef struct PlayerSettings PlayerSettings; + +#ifndef BUILD_PLAYER +#define AV_LOG_QUIET -8 +#define AV_LOG_PANIC 0 +#define AV_LOG_FATAL 8 +#define AV_LOG_ERROR 16 +#define AV_LOG_WARNING 24 +#define AV_LOG_INFO 32 +#define AV_LOG_VERBOSE 40 +#define AV_LOG_DEBUG 48 +#define AV_LOG_TRACE 56 +#endif + +#define PLAYER_ERR_OK 0 +#define PLAYER_ERR_NULLPTR 1 +#define PLAYER_ERR_SDL 2 +#define PLAYER_ERR_OOM 3 +#define PLAYER_ERR_NO_STREAM_OR_DECODER 4 +#define PLAYER_ERR_UNKNOWN_AUDIO_SAMPLE_FMT 5 +#define PLAYER_ERR_FAILED_CREATE_MUTEX 6 +#define PLAYER_ERR_WAIT_MUTEX_FAILED 7 +#define PLAYER_ERR_FAILED_CREATE_THREAD 8 +#define PLAYER_ERR_NO_DURATION 9 + +PLAYER_API const char* player_version_str(); +PLAYER_API int32_t player_version(); +/** + * @brief 设置播放器日志文件 + * + * 如果之前设置过日志文件,会关闭之前的日志文件。 + * @param filename 日志文件路径,如果为NULL则关闭之前的设置的日志文件 + * @param append 是否追加到文件末尾 + * @param max_level 最大日志级别,小于等于这个级别的日志会被记录 +*/ +PLAYER_API void set_player_log_file(const char* filename, unsigned char append, int max_level); +/** + * @brief 记录播放器日志 + * @param level 日志级别 + * @param fmt 日志格式 +*/ +PLAYER_API void player_log(int level, const char* fmt, ...); +PLAYER_API size_t player_ts_max_string_size(); +/** + * @brief 将时间戳转换为字符串 + * @param buf 用于接收字符串的缓冲区,至少需要 player_ts_max_string_size() 大小 + * @param ts 时间戳(单位:微秒) + * @return 字符串指针 +*/ +PLAYER_API char* player_ts_make_string(char* buf, int64_t ts); +/** + * @brief 返回错误代码对应的错误消息 + * @param err 错误代码 + * @return 错误消息,需要调用free释放内存 +*/ +PLAYER_API char* player_get_err_msg(int err); +/** + * @brief 返回错误代码对应的错误消息 + * @param err 错误代码(仅处理>=0的错误) + * @return 错误消息 +*/ +PLAYER_API const char* player_get_err_msg2(int err); + +/** + * @brief 创建一个播放器会话 + * @param url 要播放的文件路径 + * @param session 用于接收会话指针的指针 + * @return 错误代码 +*/ +PLAYER_API int player_create(const char* url, PlayerSession** session); + +/** + * @brief 创建一个播放器会话 + * @param url 要播放的文件路径 + * @param session 用于接收会话指针的指针 + * @param settings 播放器设置,如果为NULL会使用默认设置。注意,传入的settings需要在session销毁前保持有效,需要手动调用 player_settings_free 。 + * @return 错误代码 +*/ +PLAYER_API int player_create2(const char* url, PlayerSession** session, PlayerSettings* settings); +/** + * @brief 等待播放器初始化完成 + * @param session 播放器会话指针 + * @return 错误代码 + */ +PLAYER_API int wait_player_inited(PlayerSession* session); +/** + * @brief 开始播放 + * @param session 播放器会话指针 + * @return 错误代码 +*/ +PLAYER_API int player_play(PlayerSession* session); +/** + * @brief 暂停播放 + * @param session 播放器会话指针 + * @return 错误代码 +*/ +PLAYER_API int player_pause(PlayerSession* session); +/** + * @brief 判断播放器是否正在播放 + * @param session 播放器会话指针 + * @return 是否正在播放 +*/ +PLAYER_API int player_is_playing(PlayerSession* session); +/** + * @brief 获取当前播放时间 + * @param session 播放器会话指针 + * @param pos 用于接收当前播放时间的指针(单位:微秒) + * @return 错误代码 +*/ +PLAYER_API int player_get_duration(PlayerSession* session, int64_t* duration); +PLAYER_API void player_free(PlayerSession** session); + +/** + * @brief 初始化播放器设置,会自动设置为默认值 + * @return 播放器设置指针 +*/ +PLAYER_API PlayerSettings* player_settings_init(); +/** + * @brief 将播放器设置重置为默认值 + * @param settings 播放器设置指针 +*/ +PLAYER_API void player_settings_default(PlayerSettings* settings); +/** + * @brief 设置是否自动调整窗口大小 + * @param settings 播放器设置指针 + * @param resize 是否自动调整窗口大小 +*/ +PLAYER_API void player_settings_set_resize(PlayerSettings* settings, unsigned char resize); +/** + * @brief 设置窗口句柄 + * @param settings 播放器设置指针 + * @param hWnd 指向窗口句柄的指针 +*/ +PLAYER_API void player_settings_set_hWnd(PlayerSettings* settings, void** hWnd); +PLAYER_API void player_settings_free(PlayerSettings** settings); + +/** + * @brief 直接播放一个文件,会创建一个新的窗口并自动播放,播放结束后自动销毁。 + * + * 会阻塞当前线程。 + * @param filename 要播放的文件路径 + * @param hWnd 指向窗口句柄的指针(可选) + */ +PLAYER_API void play(const char* filename, void** hWnd); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/player_version.h b/include/player_version.h new file mode 100644 index 0000000..55934e4 --- /dev/null +++ b/include/player_version.h @@ -0,0 +1,9 @@ +#ifndef _PLAYER_VERSION_H +#define _PLAYER_VERSION_H +#define PLAYER_VERSION_MAJOR 0 +#define PLAYER_VERSION_MINOR 0 +#define PLAYER_VERSION_MICRO 1 +#define PLAYER_VERSION_REV 0 +#define PLAYER_VERSION "0.0.1.0" +#define PLAYER_VERSION_INT (((int32_t)PLAYER_VERSION_MAJOR << 24) | ((int32_t)PLAYER_VERSION_MINOR << 16) | ((int32_t)PLAYER_VERSION_MICRO << 8) | (int32_t)PLAYER_VERSION_REV) +#endif diff --git a/lib/player.lib b/lib/player.lib new file mode 100644 index 0000000..f8ceeb5 Binary files /dev/null and b/lib/player.lib differ diff --git a/utils b/utils index 5fde5e6..252c20d 160000 --- a/utils +++ b/utils @@ -1 +1 @@ -Subproject commit 5fde5e6e521bfbc74da3ec7144910e750b201b82 +Subproject commit 252c20d0688a84d59227c9934ade5085dd63d291