Skip to content

Instantly share code, notes, and snippets.

@krrr
Last active April 20, 2019 04:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krrr/3e3faf82fc825251d42e99d7007a5496 to your computer and use it in GitHub Desktop.
Save krrr/3e3faf82fc825251d42e99d7007a5496 to your computer and use it in GitHub Desktop.
fix memory leaking (32bit process out of memory, and fail to rip large files)
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <malloc.h>
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif//WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <WindowsX.h>
#include <tlhelp32.h>
struct Viewer {
// The name of the viewer executable
const char* process_name;
// The minimum number of the version of the viewer this program is built for
const char* version;
// The name of the selection for 100% zoom
const char* zoom_comboname;
// Control identifiers for the important parts of the window
int button_parent_code;
int button_first_code;
int button_next_code;
int button_zoom_code;
int area_parent_code;
int area_main_code;
};
const struct Viewer all_viewers[] = {
{
"dmmviewer.exe", // process_name
"1.2.1.0", // version
"100%", // zoom_comboname
0xE805, // button_parent_code
0x8023, // button_first_code
0x8020, // button_next_code
0x800A, // button_zoom_code
0xE900, // area_parent_code
0xE900 // area_main_code
},
{
"DLsiteViewer.exe", // process_name
"1.0.2.0", // version
"100%", // zoom_comboname
0xE805, // button_parent_code
0x8023, // button_first_code
0x8020, // button_next_code
0x800A, // button_zoom_code
0xE900, // area_parent_code
0xE900 // area_main_code
}
};
// Range of memory to search
// Could potentially be narrowed down
const LPCVOID memory_min = (LPCVOID)0x00000000;
const LPCVOID memory_max = (LPCVOID)0xffffffff;
// structs for storing .bmp header information
// char arrays to prevent padding, etc.
struct BMPHeader {
char x[14];
};
struct BMPDIB {
char x[54];
};
int create_bmp_header(struct BMPHeader* header, struct BMPDIB* dib, int w, int h) {
// Create bmp headers in the given structs
// returns the number of padding bits on each horizontal line
// Calculate the length in bytes of one line (1 byte per color channel)
int bytew = w * 3;
// Each line must be aligned to 4 bytes
if (bytew & 0x0003) {
bytew |= 0x0003;
++bytew;
}
memset(header->x, 0, sizeof(header->x));
memset(dib->x, 0, sizeof(dib->x));
// .bmp magic numbers
header->x[0] = 'B';
header->x[1] = 'M';
// Store...
// The size of the file
*((int*)(&header->x[2])) = sizeof(header->x) + sizeof(dib->x) + bytew * h;
// The bitmap offset (i.e. the size of the combined headers)
*((int*)(&header->x[10])) = sizeof(header->x) + sizeof(dib->x);
// The size of the DIB
*((int*)(&dib->x[0])) = sizeof(dib->x);
// The width of the image, in pixels
*((int*)(&dib->x[4])) = w;
// The height of the image, in pixels
*((int*)(&dib->x[8])) = h;
// The number of color planes (always 1) and the number of bits per pixel
*((int*)(&dib->x[12])) = 1 | 24 << 16;
// The size of the image, in bytes
*((int*)(&dib->x[20])) = bytew * h;
return bytew - (w * 3);
}
// Compact some stuff so it can all be sent by a pointer to get_process_window
struct process_window_helper {
// Set this before calling get_process_window as the process to match
DWORD pid;
// get_process_window will set this pointing to a visible window belonging to the process
HWND hwnd;
};
BOOL CALLBACK get_process_window(HWND hwnd, LPARAM lParam) {
// Find all windows belonging to the process
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
if (((struct process_window_helper*)lParam)->pid == pid) {
// Any given process may have many invisible top level windows
// DMMViewer has about 4, but should only have one visible (the main window)
if (IsWindowVisible(hwnd)) {
((struct process_window_helper*)lParam)->hwnd = hwnd;
return FALSE;
}
}
return TRUE;
}
void windows_error(const LPTSTR function_name) {
// Retrieve the system error message for the last-error code
LPVOID msg_buffer;
DWORD error_code = GetLastError();
if (error_code == 0) {
// No error available
return;
}
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
error_code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &msg_buffer,
0, NULL);
fprintf(stderr, "Error in function %s(): %lu: %s\n", function_name, error_code, (LPTSTR) &msg_buffer);
LocalFree(msg_buffer);
}
int main(int argc, char** argv) {
char* output_prefix = NULL;
int range_min = -1;
int range_max = -1;
int offset = -1;
int padwidth = -1;
BOOL debug = FALSE;
BOOL force_bitmap = FALSE;
int jpg_quality = -1;
HANDLE system_snapshot;
PROCESSENTRY32 process;
int target_index;
int i;
HMODULE freeimage = LoadLibrary("FreeImage.dll");
typedef long(__stdcall *FreeImage_Save_Type)(int, void**, const char*, int);
typedef void(__stdcall *FreeImage_Unload_Type)(void**);
typedef void**(__stdcall *FreeImage_ConvertFromRawBits_Type)(unsigned char*, int, int, int, unsigned, unsigned, unsigned, unsigned, long);
typedef void**(__stdcall *FreeImage_ConvertTo24Bits_Type)(void**);
FreeImage_Save_Type FreeImage_Save = NULL;
FreeImage_Unload_Type FreeImage_Unload = NULL;
FreeImage_ConvertFromRawBits_Type FreeImage_ConvertFromRawBits = NULL;
FreeImage_ConvertTo24Bits_Type FreeImage_ConvertTo24Bits = NULL;
if (freeimage == NULL) {
windows_error("LoadLibrary");
force_bitmap = TRUE;
} else {
FreeImage_Save = (FreeImage_Save_Type)GetProcAddress(freeimage, "_FreeImage_Save@16");
FreeImage_Unload = (FreeImage_Unload_Type)GetProcAddress(freeimage, "_FreeImage_Unload@4");
FreeImage_ConvertFromRawBits = (FreeImage_ConvertFromRawBits_Type)GetProcAddress(freeimage, "_FreeImage_ConvertFromRawBits@36");
FreeImage_ConvertTo24Bits = (FreeImage_ConvertTo24Bits_Type)GetProcAddress(freeimage, "_FreeImage_ConvertTo24Bits@4");
if (FreeImage_Save == NULL || FreeImage_Unload == NULL || FreeImage_ConvertFromRawBits == NULL || FreeImage_ConvertTo24Bits == NULL) {
windows_error("GetProcAddress");
force_bitmap = TRUE;
}
}
// Parse command line options
// Partially untested, partially unimplemented
for (i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-p") == 0 ||
strcmp(argv[i], "--prefix") == 0) {
// Output file prefix
// e.g. ./viewerrip -p ms1212_
// results in
// ms1212_001.bmp, ms1212_002.bmp, etc.
// Default is no prefix
if (output_prefix) {
fprintf(stderr, "Multiple output prefixes specified\n");
} else if (i + 1 >= argc) {
fprintf(stderr, "Output prefix expected\n");
} else {
size_t prefix_length = strlen(argv[i + 1]);
output_prefix = malloc(sizeof(char) * (prefix_length + 1));
strcpy(output_prefix, argv[i + 1]);
}
++i;
} else if (strcmp(argv[i], "-r") == 0 ||
strcmp(argv[i], "--range") == 0) {
// Page range (inclusive, one indexed)
// e.g. ./viewerrip -r 1 10
// will rip the first ten pages
// Default is entire file
if (range_min >= 0 && range_max >= 0) {
fprintf(stderr, "Multiple ranges specified\n");
} else if (i + 2 >= argc) {
fprintf(stderr, "Two range values expected\n");
} else {
printf("Range is not yet supported\n");
range_min = atoi(argv[i + 1]);
range_max = atoi(argv[i + 2]);
if (range_min < 0 || range_max < range_min) {
fprintf(stderr, "Invalid range specified\n");
range_min = range_max = -1;
}
}
i += 2;
} else if (strcmp(argv[i], "-o") == 0 ||
strcmp(argv[i], "--offset") == 0) {
// Naming offset
// e.g. ./viewerrip -p file -r 11 20 -o 1
// will start at page 11 but name the image file001.bmp
// Default is the first page in the range
if (offset >= 0) {
fprintf(stderr, "Multiple offsets specified\n");
} else if (i + 1 >= argc) {
fprintf(stderr, "Offset expected\n");
} else {
offset = atoi(argv[i + 1]);
if (offset < 0) {
fprintf(stderr, "Invalid offset\n");
offset = -1;
}
}
++i;
} else if (strcmp(argv[i], "-w") == 0 ||
strcmp(argv[i], "--width") == 0) {
// File number minimum width
// e.g. ./viewerrip -w 5
// will name files 00001.bmp, 00002.bmp, etc.
// Default is 3
if (padwidth > 0) {
fprintf(stderr, "Multiple widths specified\n");
} else if (i + 1 >= argc) {
fprintf(stderr, "Width expected\n");
} else {
padwidth = atoi(argv[i + 1]);
if (padwidth < 0) {
fprintf(stderr, "Invalid width\n");
padwidth = -1;
}
}
++i;
} else if (strcmp(argv[i], "-d") == 0 ||
strcmp(argv[i], "--debug") == 0) {
// Enables debugging text
if (debug) {
fprintf(stderr, "Multiple debug declarations\n");
} else {
debug = TRUE;
}
} else if (strcmp(argv[i], "-q") == 0 ||
strcmp(argv[i], "--quality") == 0) {
// Sets jpeg file quality
if (jpg_quality != 0) {
fprintf(stderr, "Multiple qualities specified\n");
} else if (i + 1 >= argc) {
fprintf(stderr, "Quality expected\n");
} else {
jpg_quality = atoi(argv[i + 1]);
if (jpg_quality < 0 || jpg_quality > 100) {
fprintf(stderr, "Invalid quality\n");
jpg_quality = -1;
}
}
++i;
}
}
if (output_prefix == NULL) {
output_prefix = "";
}
if (range_min < 0) {
range_min = 1;
}
if (range_max < 0) {
range_max = -1;
}
if (offset < 0) {
offset = range_min;
}
if (padwidth < 0) {
padwidth = 3;
}
if (jpg_quality < 0) {
jpg_quality = 82;
}
// Main program
// Get a snapshot of all the running processes
system_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
process.dwSize = sizeof(PROCESSENTRY32);
if (system_snapshot == INVALID_HANDLE_VALUE) {
fprintf(stderr, "Couldn't get snapshot of running processes\n");
windows_error("CreateToolhelp32Snapshot");
}
for (target_index = 0; target_index < sizeof(all_viewers) / sizeof(*all_viewers); ++target_index) {
if (Process32First(system_snapshot, &process) == TRUE) {
const struct Viewer target_viewer = all_viewers[target_index];
// Iterate through all the processes
while (Process32Next(system_snapshot, &process) == TRUE) {
// Find the viewer process by name
if (_stricmp(process.szExeFile, target_viewer.process_name) == 0) {
// Open the process with permission to read memory and process info
HANDLE viewer_process = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, process.th32ProcessID);
struct process_window_helper pwh;
SYSTEM_INFO system_info;
MEMORY_BASIC_INFORMATION mbi;
DWORD page_granularity;
HWND viewer_window_main;
HWND viewer_button_parent;
HWND viewer_button_first, viewer_button_next;
HWND viewer_button_zoom;
HWND viewer_area_parent;
HWND viewer_area_main;
int page;
BOOL done = FALSE;
RECT original_window_rect;
WINDOWPLACEMENT original_window_placement;
WINDOWPLACEMENT temp_window_placement;
int original_zoom_selection;
if (debug) {
printf("Found process '%s'\n", target_viewer.process_name);
}
if (viewer_process == NULL) {
fprintf(stderr, "Could not open process %s\n", process.szExeFile);
windows_error("OpenProcess");
exit(EXIT_FAILURE);
}
pwh.pid = process.th32ProcessID;
pwh.hwnd = (HWND)0xdeaddead;
EnumWindows(&get_process_window, (LPARAM)&pwh);
// Find all the subwindow by control ID
viewer_window_main = pwh.hwnd;
viewer_button_parent = GetDlgItem(viewer_window_main, target_viewer.button_parent_code);
if (viewer_button_parent == NULL) {
fprintf(stderr, "Failed to find button parent\n");
windows_error("GetDlgItem");
}
viewer_button_first = GetDlgItem(viewer_button_parent, target_viewer.button_first_code);
if (viewer_button_first == NULL) {
fprintf(stderr, "Failed to find first page button\n");
windows_error("GetDlgItem");
}
viewer_button_next = GetDlgItem(viewer_button_parent, target_viewer.button_next_code);
if (viewer_button_next == NULL) {
fprintf(stderr, "Failed to find next page button\n");
windows_error("GetDlgItem");
}
viewer_button_zoom = GetDlgItem(viewer_button_parent, target_viewer.button_zoom_code);
if (viewer_button_zoom == NULL) {
fprintf(stderr, "Failed to find zoom box\n");
windows_error("GetDlgItem");
}
viewer_area_parent = GetDlgItem(viewer_window_main, target_viewer.area_parent_code);
if (viewer_area_parent == NULL) {
fprintf(stderr, "Failed to find area parent\n");
windows_error("GetDlgItem");
}
viewer_area_main = GetDlgItem(viewer_area_parent, target_viewer.area_main_code);
if (viewer_area_main == NULL) {
fprintf(stderr, "Failed to find main area\n");
windows_error("GetDlgItem");
}
if (debug) {
printf("viewer_window_main: %p\n", viewer_window_main);
printf("viewer_button_parent: %p\n", viewer_button_parent);
printf("viewer_button_first: %p\n", viewer_button_first);
printf("viewer_button_next: %p\n", viewer_button_next);
printf("viewer_button_zoom: %p\n", viewer_button_zoom);
printf("viewer_area_main: %p\n", viewer_area_main);
}
// Store original window position and state
// Window will be shrunken to ensure window size is smaller than the image
// This is necessary so that the immage in memory will not be padded
original_window_placement.length = sizeof(original_window_placement);
GetWindowPlacement(viewer_window_main, &original_window_placement);
temp_window_placement = original_window_placement;
temp_window_placement.showCmd = SW_SHOW;
SetWindowPlacement(viewer_window_main, &temp_window_placement);
// Set zoom level to 100%
original_zoom_selection = ComboBox_GetCurSel(viewer_button_zoom);
if (ComboBox_SelectString(viewer_button_zoom, -1, target_viewer.zoom_comboname) == CB_ERR) {
fprintf(stderr, "Failed to find 100% zoom selection\n");
windows_error("ComboBox_SelectString");
}
// Programatically selecting a ComboBox option will not send a selection change message to the parent, so do that manually
SendMessage(viewer_button_parent, WM_COMMAND, MAKEWPARAM(target_viewer.button_zoom_code, CBN_SELCHANGE), (LPARAM)viewer_button_zoom);
GetWindowRect(viewer_window_main, &original_window_rect);
SetWindowPos(viewer_window_main, 0, 0, 0, 256, 256, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
GetSystemInfo(&system_info);
// Find the size of a page, so we only have to check memory on each page boundary
page_granularity = system_info.dwAllocationGranularity;
// Send a virtual click to the first page button
SendMessage(viewer_button_first, WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(1, 1));
SendMessage(viewer_button_first, WM_LBUTTONUP, MK_LBUTTON, MAKELPARAM(1, 1));
// It seems it takes some time for messages to propagate to other buttons, so wait until the next button is enabled
// This will prevent the bug where if the Viewer was opened to the last page, it would only export the first image
for (i = 0; i < 12; i++) {
Sleep(100);
if (GetWindowLong(viewer_button_next, GWL_STYLE) & WS_DISABLED) {
break;
}
}
if (i == 12) {
fprintf(stderr, "Waiting for the next button to become enabled timed out\n");
}
// Continue until the next button becomes disabled, indicating the last image
for (page = 0; !done; ++page) {
LPVOID address = (LPVOID)memory_min;
LPVOID image_address = address;
DWORD image_size = 0;
SCROLLINFO sih, siv;
int w, h;
char* image;
int number_length;
char* output_name;
FILE* output_file;
struct BMPHeader header;
struct BMPDIB dib;
int buffer_width;
int i;
// In my testing, the image is always found in the largest memory block allocated
// by the process. This isn't necessarily going to always be true, so this will likely
// fail on small images. Fortunately, comic pages tend to be rather large.
// We can further narrow the possibilities down by looking at other characteristics of the memory
// The memory type is always Private and set to Read/Write protection
while (VirtualQueryEx(viewer_process, address, &mbi, sizeof(mbi))) {
if (mbi.State == MEM_COMMIT && mbi.Type == MEM_PRIVATE && mbi.Protect == PAGE_READWRITE && mbi.RegionSize > image_size) {
image_address = mbi.BaseAddress;
image_size = mbi.RegionSize;
}
address = (char*)mbi.BaseAddress + mbi.RegionSize;
}
if (image_address == NULL || image_size == 0) {
fprintf(stderr, "Failed to query virtual memory\n");
windows_error("VirtualQueryEx");
}
if (debug) {
printf("Most likely image location at %p (%lu bytes)\n", image_address, image_size);
}
// As a hack, the dimensions of the images are determined by
// looking at the size of the scroll area
// This requires that the image area be smaller than the image
sih.cbSize = sizeof(sih);
sih.fMask = SIF_ALL;
siv.cbSize = sizeof(siv);
siv.fMask = SIF_ALL;
GetScrollInfo(viewer_area_main, SB_HORZ, &sih);
GetScrollInfo(viewer_area_main, SB_VERT, &siv);
w = sih.nMax - sih.nMin + 1;
h = siv.nMax - siv.nMin + 1;
if (debug) {
printf("%i x %i pixels\n", w, h);
}
// Allocate memory to copy image into
image = malloc(sizeof(int) * w * h);
if (image == NULL) {
fprintf(stderr, "Could not allocate memory for image\n");
continue;
}
// Read the image memory (image is stored in 4 byte pixels, 0xrrggbbFF)
if (ReadProcessMemory(viewer_process, image_address, (LPVOID)image, w * h * 4, NULL) == 0) {
if (debug) {
printf("Image uses %i of %lu allocated bytes\n", w * h * 4, image_size);
}
printf("On page %i:\n", page);
windows_error("ReadProcessMemory");
continue;
}
if (debug) {
printf("Image uses %i of %lu allocated bytes\n", w * h * 4, image_size);
}
// Once image memory is ours, 'click' to the next image
SendMessage(viewer_button_next, WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(1, 1));
SendMessage(viewer_button_next, WM_LBUTTONUP, MK_LBUTTON, MAKELPARAM(1, 1));
// If the next button is disabled, we reached the end; grab one last image and finish
done = GetWindowLong(viewer_button_next, GWL_STYLE) & WS_DISABLED;
number_length = (int)log10((double)page + offset);
number_length = number_length > padwidth ? number_length : padwidth;
if (!force_bitmap) {
output_name = malloc(sizeof(char) * (strlen(output_prefix) + number_length + strlen(".jpg") + 1));
sprintf(output_name, "%s%0*i.jpg", output_prefix, padwidth, page + offset);
void** rgba_data = FreeImage_ConvertFromRawBits((unsigned char*)image, w, h, 4 * w, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, TRUE);
if (rgba_data == NULL) {
fprintf(stderr, "Failed to create image with FreeImage, falling back to .bmp export");
force_bitmap = TRUE;
} else {
void** rgb_data = FreeImage_ConvertTo24Bits(rgba_data);
if (rgb_data == NULL) {
fprintf(stderr, "Failed to create image with FreeImage, falling back to .bmp export");
force_bitmap = TRUE;
} else {
FreeImage_Save(2/*JPG*/, rgb_data, output_name, jpg_quality | 0x40000/*No metadata*/ | 0x20000/*Optimize*/);
FreeImage_Unload(rgb_data);
}
FreeImage_Unload(rgba_data);
}
} else {
output_name = malloc(sizeof(char) * (strlen(output_prefix) + number_length + strlen(".bmp") + 1));
sprintf(output_name, "%s%0*i.bmp", output_prefix, padwidth, page + offset);
output_file = fopen(output_name, "wb+");
if (!output_file) {
fprintf(stderr, "Failed to create file '%s'\n", output_name);
continue;
}
buffer_width = create_bmp_header(&header, &dib, w, h);
fwrite(header.x, sizeof(header.x), 1, output_file);
fwrite(dib.x, sizeof(dib.x), 1, output_file);
// .bmps are mirrored vertically
for (i = h - 1; i >= 0; --i) {
int j;
for (j = 0; j < w; ++j) {
fwrite(((char*)(image + (i * w + j) * 4)), 3, 1, output_file);
}
// Write any padding bytes
for (j = 0; j < buffer_width; ++j) {
fputc(0xff, output_file);
}
}
fclose(output_file);
}
free(output_name);
free(image);
}
// Restore window size and state
SetWindowPos(viewer_window_main, 0, 0, 0, original_window_rect.right - original_window_rect.left, original_window_rect.top - original_window_rect.bottom, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
SetWindowPlacement(viewer_window_main, &original_window_placement);
// Restore zoom level
ComboBox_SetCurSel(viewer_button_zoom, original_zoom_selection);
SendMessage(viewer_button_parent, WM_COMMAND, MAKEWPARAM(target_viewer.button_zoom_code, CBN_SELCHANGE), (LPARAM)viewer_button_zoom);
CloseHandle(viewer_process);
exit(EXIT_SUCCESS);
}
}
} else {
windows_error("Process32First");
exit(EXIT_FAILURE);
}
}
fprintf(stderr, "Could not find a running supported viewer\n");
exit(EXIT_FAILURE);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment