From 5c4779ee1614b5d4a5688a78d03aa6c59d5ab2d8 Mon Sep 17 00:00:00 2001 From: Jay-Day <93jd@pm.me> Date: Mon, 23 Mar 2026 15:40:28 -0400 Subject: [PATCH 1/2] Add remove background music option and SmashRemix 2.0.1 ROM database entries Add GUI checkbox to mute background music during conversion using GameShark cheat injection via the mupen64plus CoreAddCheat API. Currently uses the Smash Remix music volume address (81462BE2). Also add SmashRemix 2.0.1 and 2.0.1 (PAL60) entries to mupen64plus.ini with CountPerOp=1, which is required for correct emulation timing and prevents desync. Co-Authored-By: Claude Opus 4.6 (1M context) --- Data/mupen64plus.ini | 12 ++++++++++++ src/converter.cpp | 12 ++++++++++++ src/converter.h | 1 + src/emulator.cpp | 16 ++++++++++++++++ src/emulator.h | 12 ++++++++++++ src/frame_capture.cpp | 8 ++++---- src/gui_main.cpp | 13 ++++++++++++- src/gui_resources.h | 2 ++ 8 files changed, 71 insertions(+), 5 deletions(-) diff --git a/Data/mupen64plus.ini b/Data/mupen64plus.ini index 55372af..a66751a 100644 --- a/Data/mupen64plus.ini +++ b/Data/mupen64plus.ini @@ -14149,6 +14149,18 @@ CRC=D68CEFC0 0B81D955 RefMD5=5AAC6E652C5CF1E37A466AC0073E88CA CountPerOp=1 +[2B2D6B295106C54216B7FC7A2F14346E] +GoodName=SmashRemix2.0.1 +CRC=34EE4749 1833C64C +RefMD5=5AAC6E652C5CF1E37A466AC0073E88CA +CountPerOp=1 + +[0CFDA9AB8E7DEE5B088385D2576369E6] +GoodName=SmashRemix2.0.1 (PAL60) +CRC=96D0976A 2FB7654F +RefMD5=5AAC6E652C5CF1E37A466AC0073E88CA +CountPerOp=1 + [B8D4B92E66A312708626B3216DE07A3A] GoodName=Snobow Kids (J) [!] CRC=84FC04FF B1253CE9 diff --git a/src/converter.cpp b/src/converter.cpp index 5ec90ac..58a97c0 100644 --- a/src/converter.cpp +++ b/src/converter.cpp @@ -351,6 +351,18 @@ bool convert_one(const std::string& krec_path, const std::string& output_path, emu.apply_deterministic_settings(); + // Apply GameShark cheats if requested + if (config.remove_music) { + m64p_cheat_code no_music[] = { + {0x81462BE2, 0x0000}, + }; + if (emu.apply_cheat("NoMusic", no_music, 1)) { + converter_log(LOG_INFO, "Applied 'Remove Background Music' cheat."); + } else { + converter_log(LOG_WARNING, "Warning: failed to apply music removal cheat."); + } + } + // Setup FFmpeg encoder config (video only, to temp file) // Encoder is opened lazily on first frame to match actual render dimensions FFmpegEncoder encoder; diff --git a/src/converter.h b/src/converter.h index 9d1397c..6db60de 100644 --- a/src/converter.h +++ b/src/converter.h @@ -41,6 +41,7 @@ struct AppConfig { std::string encoder = "libx264"; // FFmpeg codec name bool batch = false; bool verbose = false; + bool remove_music = false; }; // Get the directory containing the executable diff --git a/src/emulator.cpp b/src/emulator.cpp index ab76bf0..656f029 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -78,6 +78,9 @@ bool Emulator::load_core(const std::string& path) { RESOLVE(config_open_section, ptr_ConfigOpenSection, "ConfigOpenSection"); RESOLVE(config_set_parameter, ptr_ConfigSetParameter, "ConfigSetParameter"); + // Optional cheat API + core_add_cheat = (ptr_CoreAddCheat)GET_PROC(core_handle, "CoreAddCheat"); + // Optional RMG-K extension set_pif_callback_fn = (ptr_set_pif_sync_callback)GET_PROC(core_handle, "set_pif_sync_callback"); if (!set_pif_callback_fn) { @@ -333,6 +336,19 @@ void Emulator::apply_deterministic_settings() { // via GLideN64.ini in configure_gliden64(), called during init(). } +bool Emulator::apply_cheat(const char* name, m64p_cheat_code* codes, int num_codes) { + if (!core_add_cheat) { + fprintf(stderr, "Warning: CoreAddCheat not available in this core build\n"); + return false; + } + m64p_error ret = core_add_cheat(name, codes, num_codes); + if (ret != M64ERR_SUCCESS) { + fprintf(stderr, "Error: CoreAddCheat('%s') failed (error %d)\n", name, ret); + return false; + } + return true; +} + void Emulator::configure_controllers_for_replay(int num_players) { m64p_handle section; diff --git a/src/emulator.h b/src/emulator.h index 4e47ab9..61ccffb 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -101,6 +101,12 @@ struct pif { typedef void (*pif_sync_callback_t)(struct pif*); +// Cheat code structure +typedef struct { + uint32_t address; + int value; +} m64p_cheat_code; + } // extern "C" // Mupen64plus core function pointer types @@ -128,6 +134,10 @@ typedef m64p_error (*ptr_ConfigSetDefaultString)(m64p_handle, const char*, const typedef m64p_error (*ptr_PluginStartup)(m64p_dynlib_handle, void*, ptr_DebugCallback); typedef m64p_error (*ptr_PluginShutdown)(void); +// Cheat API +typedef m64p_error (*ptr_CoreAddCheat)(const char*, m64p_cheat_code*, int); +typedef m64p_error (*ptr_CoreCheatEnabled)(const char*, int); + // RMG-K extension typedef void (*ptr_set_pif_sync_callback)(pif_sync_callback_t); @@ -158,6 +168,7 @@ class Emulator { bool open_rom(const std::string& rom_path); bool attach_plugins(); void apply_deterministic_settings(); + bool apply_cheat(const char* name, m64p_cheat_code* codes, int num_codes); void configure_controllers_for_replay(int num_players); void set_pif_callback(pif_sync_callback_t callback); void set_frame_callback(m64p_frame_callback callback); @@ -186,6 +197,7 @@ class Emulator { ptr_ConfigOpenSection config_open_section = nullptr; ptr_ConfigSetParameter config_set_parameter = nullptr; ptr_set_pif_sync_callback set_pif_callback_fn = nullptr; + ptr_CoreAddCheat core_add_cheat = nullptr; ptr_ReadScreen2 read_screen2 = nullptr; bool verbose = false; diff --git a/src/frame_capture.cpp b/src/frame_capture.cpp index 553c3c4..dadd64a 100644 --- a/src/frame_capture.cpp +++ b/src/frame_capture.cpp @@ -41,7 +41,7 @@ static FFmpegConfig s_ff_config; static bool s_encoder_opened = false; static int s_captured_frames = 0; static int s_total_frames = 0; -static bool s_speed_limiter_disabled = false; +static bool s_speed_set = false; static ProgressCallback s_progress_callback; static std::atomic* s_cancel_flag = nullptr; @@ -194,7 +194,7 @@ void frame_capture_init(Emulator* emu, FFmpegEncoder* encoder, const FFmpegConfi s_encoder_opened = false; s_captured_frames = 0; s_total_frames = total_frames; - s_speed_limiter_disabled = false; + s_speed_set = false; // Keep buffers allocated across batch runs to avoid reallocation s_progress_callback = nullptr; s_cancel_flag = nullptr; @@ -235,10 +235,10 @@ void frame_capture_callback(unsigned int frame_index) { } // Disable speed limiter on first frame for maximum conversion speed - if (!s_speed_limiter_disabled && s_emu) { + if (!s_speed_set && s_emu) { int limiter = 0; // 0 = off s_emu->core_do_command(M64CMD_CORE_STATE_SET, M64CORE_SPEED_LIMITER, &limiter); - s_speed_limiter_disabled = true; + s_speed_set = true; } // Reset PIF sync flag for next frame diff --git a/src/gui_main.cpp b/src/gui_main.cpp index 88cf7f5..32f26c9 100644 --- a/src/gui_main.cpp +++ b/src/gui_main.cpp @@ -48,6 +48,7 @@ static HWND g_msaa_value = nullptr; static HWND g_aniso_slider = nullptr; static HWND g_aniso_value = nullptr; static HWND g_encoder_combo = nullptr; +static HWND g_remove_music_check = nullptr; static HWND g_verbose_check = nullptr; static HWND g_convert_btn = nullptr; static HWND g_cancel_btn = nullptr; @@ -240,6 +241,8 @@ static void SaveSettings() { WritePrivateProfileStringW(sec, L"Batch", batch ? L"1" : L"0", file); bool verbose = (SendMessageW(g_verbose_check, BM_GETCHECK, 0, 0) == BST_CHECKED); WritePrivateProfileStringW(sec, L"Verbose", verbose ? L"1" : L"0", file); + bool remove_music = (SendMessageW(g_remove_music_check, BM_GETCHECK, 0, 0) == BST_CHECKED); + WritePrivateProfileStringW(sec, L"RemoveMusic", remove_music ? L"1" : L"0", file); // Resolution (save index) int res_sel = (int)SendMessageW(g_resolution_combo, CB_GETCURSEL, 0, 0); @@ -302,6 +305,8 @@ static void LoadSettings() { SendMessageW(g_batch_check, BM_SETCHECK, batch ? BST_CHECKED : BST_UNCHECKED, 0); int verbose = GetPrivateProfileIntW(sec, L"Verbose", 0, file); SendMessageW(g_verbose_check, BM_SETCHECK, verbose ? BST_CHECKED : BST_UNCHECKED, 0); + int remove_music = GetPrivateProfileIntW(sec, L"RemoveMusic", 0, file); + SendMessageW(g_remove_music_check, BM_SETCHECK, remove_music ? BST_CHECKED : BST_UNCHECKED, 0); // Resolution int res_sel = GetPrivateProfileIntW(sec, L"Resolution", 1, file); @@ -497,6 +502,11 @@ static void CreateControls(HWND hwnd) { SendMessageW(g_aniso_value, WM_SETFONT, (WPARAM)g_font, TRUE); y += ROW_H + GAP + 4; + // --- Game-specific options --- + g_remove_music_check = CreateCheck(hwnd, L"Remove background music (SSB64)", + IDC_REMOVE_MUSIC_CHECK, EDIT_X, y, 280, ROW_H); + y += ROW_H + GAP; + // --- Verbose + buttons --- g_verbose_check = CreateCheck(hwnd, L"Verbose logging", IDC_VERBOSE_CHECK, EDIT_X, y, 160, ROW_H); @@ -625,6 +635,7 @@ static AppConfig ReadConfig() { cfg.output_path = GetEditText(g_output_path); cfg.batch = (SendMessageW(g_batch_check, BM_GETCHECK, 0, 0) == BST_CHECKED); cfg.verbose = (SendMessageW(g_verbose_check, BM_GETCHECK, 0, 0) == BST_CHECKED); + cfg.remove_music = (SendMessageW(g_remove_music_check, BM_GETCHECK, 0, 0) == BST_CHECKED); // Default paths relative to exe std::string exe_dir = get_exe_dir(); @@ -994,7 +1005,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) { // Calculate window size to fit client area const int CLIENT_W = 620; - const int CLIENT_H = 670; + const int CLIENT_H = 700; RECT rc = { 0, 0, CLIENT_W, CLIENT_H }; AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME | WS_MAXIMIZEBOX), FALSE, 0); diff --git a/src/gui_resources.h b/src/gui_resources.h index f67ccbf..ce52318 100644 --- a/src/gui_resources.h +++ b/src/gui_resources.h @@ -21,6 +21,8 @@ #define IDC_ANISO_VALUE 208 #define IDC_ENCODER 209 +#define IDC_REMOVE_MUSIC_CHECK 301 + #define IDC_VERBOSE_CHECK 401 #define IDC_CONVERT_BTN 402 #define IDC_CANCEL_BTN 403 From 91fd3588547240471dff97ed5ec0c2fd5311bf01 Mon Sep 17 00:00:00 2001 From: Jay-Day <93jd@pm.me> Date: Tue, 12 May 2026 00:03:03 -0400 Subject: [PATCH 2/2] Add selectable file list to batch mode Replaces unconditional folder scan with a checkable ListView showing filename, game name, and recording date parsed from each krec header. Lets users skip specific recordings when batch converting. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/converter.h | 2 + src/gui_main.cpp | 203 ++++++++++++++++++++++++++++++++++++++++++-- src/gui_resources.h | 3 + 3 files changed, 199 insertions(+), 9 deletions(-) diff --git a/src/converter.h b/src/converter.h index 6db60de..33f8fcf 100644 --- a/src/converter.h +++ b/src/converter.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include @@ -40,6 +41,7 @@ struct AppConfig { int aniso = 0; // 0=off, 2, 4, 8, 16 std::string encoder = "libx264"; // FFmpeg codec name bool batch = false; + std::vector batch_files; // selected files for batch mode bool verbose = false; bool remove_music = false; }; diff --git a/src/gui_main.cpp b/src/gui_main.cpp index 32f26c9..a07ead7 100644 --- a/src/gui_main.cpp +++ b/src/gui_main.cpp @@ -39,6 +39,9 @@ static HFONT g_font = nullptr; static HWND g_rom_path = nullptr; static HWND g_input_path = nullptr; static HWND g_batch_check = nullptr; +static HWND g_batch_list = nullptr; +static HWND g_batch_select_all = nullptr; +static HWND g_batch_deselect_all = nullptr; static HWND g_output_path = nullptr; static HWND g_resolution_combo = nullptr; static HWND g_quality_combo = nullptr; @@ -415,10 +418,43 @@ static void CreateControls(HWND hwnd) { CreateBtn(hwnd, L"Browse...", IDC_INPUT_BROWSE, EDIT_X + EDIT_W + GAP, y, BTN_W, ROW_H); y += ROW_H + GAP; - g_batch_check = CreateCheck(hwnd, L"Batch mode (process all .krec in folder)", + g_batch_check = CreateCheck(hwnd, L"Batch mode (process .krec files in folder)", IDC_BATCH_CHECK, EDIT_X, y, EDIT_W, ROW_H); y += ROW_H + GAP; + // Batch file list (hidden by default, shown when batch mode is checked) + g_batch_select_all = CreateBtn(hwnd, L"Select All", IDC_BATCH_SELECT_ALL, + EDIT_X, y, 80, ROW_H); + g_batch_deselect_all = CreateBtn(hwnd, L"Deselect All", IDC_BATCH_DESELECT_ALL, + EDIT_X + 86, y, 90, ROW_H); + ShowWindow(g_batch_select_all, SW_HIDE); + ShowWindow(g_batch_deselect_all, SW_HIDE); + y += ROW_H + 2; + + g_batch_list = CreateWindowExW(WS_EX_CLIENTEDGE, WC_LISTVIEWW, L"", + WS_CHILD | LVS_REPORT | LVS_NOSORTHEADER | LVS_SHOWSELALWAYS, + EDIT_X, y, CLIENT_W - EDIT_X - MARGIN, 150, + hwnd, (HMENU)(INT_PTR)IDC_BATCH_LIST, nullptr, nullptr); + SendMessageW(g_batch_list, WM_SETFONT, (WPARAM)g_font, TRUE); + ListView_SetExtendedListViewStyle(g_batch_list, + LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT); + { + int list_w = CLIENT_W - EDIT_X - MARGIN - 24; // leave room for scrollbar + LVCOLUMNW col = {}; + col.mask = LVCF_TEXT | LVCF_WIDTH; + col.pszText = (LPWSTR)L"Filename"; + col.cx = list_w * 5 / 12; + SendMessageW(g_batch_list, LVM_INSERTCOLUMNW, 0, (LPARAM)&col); + col.pszText = (LPWSTR)L"Game"; + col.cx = list_w * 4 / 12; + SendMessageW(g_batch_list, LVM_INSERTCOLUMNW, 1, (LPARAM)&col); + col.pszText = (LPWSTR)L"Date"; + col.cx = list_w * 3 / 12; + SendMessageW(g_batch_list, LVM_INSERTCOLUMNW, 2, (LPARAM)&col); + } + ShowWindow(g_batch_list, SW_HIDE); + y += 150 + GAP; + CreateLabel(hwnd, L"Output:", MARGIN, y + 2, LBL_W, ROW_H); g_output_path = CreateEdit(hwnd, IDC_OUTPUT_PATH, EDIT_X, y, EDIT_W, ROW_H); CreateBtn(hwnd, L"Browse...", IDC_OUTPUT_BROWSE, EDIT_X + EDIT_W + GAP, y, BTN_W, ROW_H); @@ -503,7 +539,7 @@ static void CreateControls(HWND hwnd) { y += ROW_H + GAP + 4; // --- Game-specific options --- - g_remove_music_check = CreateCheck(hwnd, L"Remove background music (SSB64)", + g_remove_music_check = CreateCheck(hwnd, L"Remove background music (Remix 2.0.1)", IDC_REMOVE_MUSIC_CHECK, EDIT_X, y, 280, ROW_H); y += ROW_H + GAP; @@ -551,6 +587,110 @@ static void CreateControls(HWND hwnd) { SendMessageW(g_log_edit, WM_SETFONT, (WPARAM)g_font, TRUE); } +// --- Batch file list helpers --- + +struct KrecHeader { + std::string game; + uint32_t timestamp = 0; +}; + +static KrecHeader ReadKrecHeader(const std::string& path) { + KrecHeader hdr; + FILE* f = fopen(path.c_str(), "rb"); + if (!f) return hdr; + // Game name at offset 132, 128 bytes + char buf[128] = {}; + if (fseek(f, 132, SEEK_SET) == 0) + fread(buf, 1, 127, f); + buf[127] = 0; + hdr.game = buf; + // Timestamp at offset 260, 4 bytes + if (fseek(f, 260, SEEK_SET) == 0) + fread(&hdr.timestamp, 4, 1, f); + fclose(f); + return hdr; +} + +static void PopulateBatchList() { + ListView_DeleteAllItems(g_batch_list); + std::string dir = GetEditText(g_input_path); + if (dir.empty() || !fs::is_directory(dir)) return; + + // Collect entries with header info + struct KrecEntry { + std::wstring filename; + std::wstring game; + std::wstring date; + uint32_t timestamp; + }; + std::vector entries; + for (auto& entry : fs::directory_iterator(dir)) { + if (entry.is_regular_file() && entry.path().extension() == ".krec") { + KrecHeader hdr = ReadKrecHeader(entry.path().string()); + std::wstring date_str; + if (hdr.timestamp > 0) { + time_t t = (time_t)hdr.timestamp; + struct tm* tm = localtime(&t); + if (tm) { + char tbuf[64]; + strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M", tm); + date_str = Utf8ToWide(tbuf); + } + } + entries.push_back({entry.path().filename().wstring(), + Utf8ToWide(hdr.game), date_str, hdr.timestamp}); + } + } + // Sort newest first + std::sort(entries.begin(), entries.end(), + [](const KrecEntry& a, const KrecEntry& b) { + return a.timestamp > b.timestamp; + }); + + SendMessageW(g_batch_list, WM_SETREDRAW, FALSE, 0); + for (size_t i = 0; i < entries.size(); i++) { + LVITEMW item = {}; + item.mask = LVIF_TEXT; + item.iItem = (int)i; + item.pszText = (LPWSTR)entries[i].filename.c_str(); + SendMessageW(g_batch_list, LVM_INSERTITEMW, 0, (LPARAM)&item); + // Game name column + LVITEMW sub = {}; + sub.iItem = (int)i; + sub.iSubItem = 1; + sub.pszText = (LPWSTR)entries[i].game.c_str(); + SendMessageW(g_batch_list, LVM_SETITEMTEXTW, (WPARAM)i, (LPARAM)&sub); + // Date column + sub.iSubItem = 2; + sub.pszText = (LPWSTR)entries[i].date.c_str(); + SendMessageW(g_batch_list, LVM_SETITEMTEXTW, (WPARAM)i, (LPARAM)&sub); + ListView_SetCheckState(g_batch_list, (int)i, TRUE); + } + SendMessageW(g_batch_list, WM_SETREDRAW, TRUE, 0); + InvalidateRect(g_batch_list, nullptr, TRUE); +} + +static void UpdateBatchListVisibility() { + bool batch = (SendMessageW(g_batch_check, BM_GETCHECK, 0, 0) == BST_CHECKED); + int show = batch ? SW_SHOW : SW_HIDE; + ShowWindow(g_batch_list, show); + ShowWindow(g_batch_select_all, show); + ShowWindow(g_batch_deselect_all, show); + if (batch) { + PopulateBatchList(); + // Adjust output path to folder (strip filename if present) + std::string out = GetEditText(g_output_path); + if (!out.empty()) { + fs::path p(out); + if (!fs::is_directory(p) && p.has_parent_path()) { + SetEditText(g_output_path, p.parent_path().string()); + } + } + } else { + ListView_DeleteAllItems(g_batch_list); + } +} + // --- Worker thread --- static void WorkerThread(AppConfig config) { @@ -578,11 +718,7 @@ static void WorkerThread(AppConfig config) { std::vector krec_files; if (config.batch) { - for (auto& entry : fs::directory_iterator(config.input_path)) { - if (entry.is_regular_file() && entry.path().extension() == ".krec") { - krec_files.push_back(entry.path().string()); - } - } + krec_files = std::move(config.batch_files); } else { krec_files.push_back(config.input_path); } @@ -634,6 +770,22 @@ static AppConfig ReadConfig() { cfg.input_path = GetEditText(g_input_path); cfg.output_path = GetEditText(g_output_path); cfg.batch = (SendMessageW(g_batch_check, BM_GETCHECK, 0, 0) == BST_CHECKED); + if (cfg.batch) { + int count = ListView_GetItemCount(g_batch_list); + for (int i = 0; i < count; i++) { + if (ListView_GetCheckState(g_batch_list, i)) { + wchar_t buf[MAX_PATH] = {}; + LVITEMW lvi = {}; + lvi.iSubItem = 0; + lvi.pszText = buf; + lvi.cchTextMax = MAX_PATH; + SendMessageW(g_batch_list, LVM_GETITEMTEXTW, (WPARAM)i, (LPARAM)&lvi); + std::string filename = WideToUtf8(buf); + cfg.batch_files.push_back( + (fs::path(cfg.input_path) / filename).string()); + } + } + } cfg.verbose = (SendMessageW(g_verbose_check, BM_GETCHECK, 0, 0) == BST_CHECKED); cfg.remove_music = (SendMessageW(g_remove_music_check, BM_GETCHECK, 0, 0) == BST_CHECKED); @@ -706,6 +858,10 @@ static void StartConversion() { MessageBoxW(g_hwnd, L"In batch mode, output must be an existing directory.", L"Validation Error", MB_ICONWARNING); return; } + if (cfg.batch && cfg.batch_files.empty()) { + MessageBoxW(g_hwnd, L"No files selected for conversion.", L"Validation Error", MB_ICONWARNING); + return; + } if (!cfg.batch && !fs::is_regular_file(cfg.input_path)) { MessageBoxW(g_hwnd, L"Input file does not exist.", L"Validation Error", MB_ICONWARNING); return; @@ -738,6 +894,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara case WM_CREATE: CreateControls(hwnd); LoadSettings(); + UpdateBatchListVisibility(); return 0; case WM_HSCROLL: { @@ -764,6 +921,19 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara return 0; } + // Batch checkbox toggled -> show/hide file list + if (id == IDC_BATCH_CHECK && code == BN_CLICKED) { + UpdateBatchListVisibility(); + return 0; + } + + // Input path lost focus while in batch mode -> repopulate list + if (id == IDC_INPUT_PATH && code == EN_KILLFOCUS) { + bool batch = (SendMessageW(g_batch_check, BM_GETCHECK, 0, 0) == BST_CHECKED); + if (batch) PopulateBatchList(); + return 0; + } + switch (id) { case IDC_ROM_BROWSE: { std::wstring dir = GetEditDir(g_rom_path); @@ -779,7 +949,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara if (batch) { auto f = BrowseFolder(hwnd, L"Select input folder", dir.empty() ? nullptr : dir.c_str()); - if (!f.empty()) SetWindowTextW(g_input_path, f.c_str()); + if (!f.empty()) { + SetWindowTextW(g_input_path, f.c_str()); + PopulateBatchList(); + } } else { auto f = BrowseFile(hwnd, L"Select .krec file", L"Krec Files (*.krec)\0*.krec\0All Files\0*.*\0", false, @@ -788,6 +961,18 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara } break; } + case IDC_BATCH_SELECT_ALL: { + int count = ListView_GetItemCount(g_batch_list); + for (int i = 0; i < count; i++) + ListView_SetCheckState(g_batch_list, i, TRUE); + break; + } + case IDC_BATCH_DESELECT_ALL: { + int count = ListView_GetItemCount(g_batch_list); + for (int i = 0; i < count; i++) + ListView_SetCheckState(g_batch_list, i, FALSE); + break; + } case IDC_OUTPUT_BROWSE: { std::wstring dir = GetEditDir(g_output_path); bool batch = (SendMessageW(g_batch_check, BM_GETCHECK, 0, 0) == BST_CHECKED); @@ -1005,7 +1190,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) { // Calculate window size to fit client area const int CLIENT_W = 620; - const int CLIENT_H = 700; + const int CLIENT_H = 882; RECT rc = { 0, 0, CLIENT_W, CLIENT_H }; AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME | WS_MAXIMIZEBOX), FALSE, 0); diff --git a/src/gui_resources.h b/src/gui_resources.h index ce52318..fc4d87e 100644 --- a/src/gui_resources.h +++ b/src/gui_resources.h @@ -11,6 +11,9 @@ #define IDC_BATCH_CHECK 105 #define IDC_OUTPUT_PATH 106 #define IDC_OUTPUT_BROWSE 107 +#define IDC_BATCH_LIST 108 +#define IDC_BATCH_SELECT_ALL 109 +#define IDC_BATCH_DESELECT_ALL 110 #define IDC_RESOLUTION 201 #define IDC_QUALITY 202