You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
523 lines
15 KiB
C
523 lines
15 KiB
C
// Run an sql command without any input. Useful if you want to use wine debugger
|
|
// where it's impossible to pipe in a file/program to stdin.
|
|
|
|
// #define SINGLE_COMMAND L"select TABLE_NAME from INFORMATION_SCHEMA.TABLES;"
|
|
// #define SINGLE_COMMAND L"select * from GameData;"
|
|
|
|
#define __USE_MINGW_ANSI_STDIO 0
|
|
#include <stdio.h>
|
|
#include <wchar.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
// setmode
|
|
#include <fcntl.h>
|
|
#include <io.h>
|
|
|
|
#define DBINITCONSTANTS
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#ifndef UNICODE
|
|
#define UNICODE
|
|
#endif
|
|
#include <windows.h>
|
|
#include <objbase.h>
|
|
#include <guiddef.h>
|
|
#include <oledb.h>
|
|
|
|
extern BOOLEAN NTAPI SystemFunction036(PVOID, ULONG);
|
|
|
|
static const GUID CLSID_SQLSERVERCE_3_5 =
|
|
{0xf49c559d, 0xe9e5, 0x467c, {0x8c, 0x18, 0x33, 0x26, 0xaa, 0xe4, 0xeb, 0xcc}};
|
|
static const GUID CLSID_Engine =
|
|
{0xa9d3060d, 0x3526, 0x4538, {0xb1, 0x3a, 0x19, 0x13, 0x56, 0x8d, 0xaa, 0x0d}};
|
|
static const GUID IID_ISSCEEngine =
|
|
{0x10ec3e45,0x0870,0x4d7b, {0x9a,0x2d,0xf4,0xf8,0x1b,0x6b,0x7f,0xa2}};
|
|
|
|
static const GUID DBPROPSET_SSCE_DBINIT =
|
|
{0x2b9ab5ba, 0x4f6c, 0x4ddd, {0xbf, 0x18, 0x24, 0xdd, 0x4b, 0xd4, 0x18, 0x48}};
|
|
|
|
#define DBPROP_SSCE_DBPASSWORD 0x1FBL
|
|
#define DBPROP_SSCE_MAX_DATABASE_SIZE 0x20BL
|
|
|
|
#ifdef __WINE_WINDOWS_H
|
|
static const DBID DB_NULLID = {{{0x00000000L,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}}, 0, {(LPOLESTR) NULL}};
|
|
# ifndef __IAccessor_INTERFACE_DEFINED__
|
|
enum DBACCESSORFLAGSENUM
|
|
{
|
|
DBACCESSOR_INVALID = 0,
|
|
DBACCESSOR_PASSBYREF = 0x1,
|
|
DBACCESSOR_ROWDATA = 0x2,
|
|
DBACCESSOR_PARAMETERDATA = 0x4,
|
|
DBACCESSOR_OPTIMIZED = 0x8,
|
|
DBACCESSOR_INHERITED = 0x10
|
|
};
|
|
# endif
|
|
#endif
|
|
|
|
typedef interface ISSCEEngine ISSCEEngine;
|
|
interface ISSCEEngine
|
|
{
|
|
CONST_VTBL struct ISSCEEngineVtbl *lpVtbl;
|
|
};
|
|
|
|
typedef enum REPAIROPTION { DELETECORRUPTED, RECOVERCORRUPTED } REPAIROPTION;
|
|
typedef struct ISSCEEngineVtbl
|
|
{
|
|
BEGIN_INTERFACE
|
|
|
|
HRESULT (STDMETHODCALLTYPE *QueryInterface)(ISSCEEngine* thiz, REFIID riid, void **obj);
|
|
ULONG (STDMETHODCALLTYPE *AddRef)(ISSCEEngine* This);
|
|
ULONG (STDMETHODCALLTYPE *Release)(ISSCEEngine* This);
|
|
|
|
HRESULT (STDMETHODCALLTYPE *CompactDatabase)(
|
|
ISSCEEngine* thiz, BSTR src_conn, BSTR dst_conn);
|
|
|
|
HRESULT (STDMETHODCALLTYPE *get_ErrorRecords)(
|
|
ISSCEEngine* thiz, /*ISSCEErrors*/ void **val);
|
|
|
|
HRESULT (STDMETHODCALLTYPE *CreateDatabase)(ISSCEEngine* thiz, BSTR conn);
|
|
|
|
HRESULT (STDMETHODCALLTYPE *Repair)(
|
|
ISSCEEngine* thiz, BSTR src, BSTR dst, REPAIROPTION opt);
|
|
|
|
// doesn't do anything on wine?
|
|
HRESULT (STDMETHODCALLTYPE *UpgradeDatabase)(
|
|
ISSCEEngine* thiz, BSTR src, BSTR dst);
|
|
|
|
END_INTERFACE
|
|
} ISSCEEngineVtbl;
|
|
|
|
#define DB_PW \
|
|
L"\u00C7\u00B0\u00A5\u00E0\u00F2\u00C5\u00C5\u00C7\u00C9\u00E0\u00F1\u00F1\u00F8"
|
|
#define COM(x, fun, ...) ((x)->lpVtbl->fun((x), ##__VA_ARGS__))
|
|
#define ALEN(x) (sizeof(x) / sizeof(x[0]))
|
|
|
|
#define EXIT_OK 0
|
|
#define EXIT_USER 1 // invalid cmd line usage
|
|
#define EXIT_COMM 2 // read invalid data on stdin
|
|
#define EXIT_UNREC 3 // unrecoverable error
|
|
|
|
static void check(HRESULT hr, const char* file, int line)
|
|
{
|
|
if (!FAILED(hr)) return;
|
|
|
|
IErrorInfo* ei;
|
|
GetErrorInfo(0, &ei);
|
|
if (ei != NULL)
|
|
{
|
|
BSTR str;
|
|
COM(ei, GetDescription, &str);
|
|
fprintf(stderr, "%s:%d: HRESULT %x: %ls\n", file, line, (unsigned) hr, str);
|
|
SysFreeString(str);
|
|
COM(ei, Release);
|
|
}
|
|
else
|
|
fprintf(stderr, "%s:%d: HRESULT %x\n", file, line, (unsigned) hr);
|
|
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
exit(EXIT_UNREC);
|
|
}
|
|
#define CHECK(x) check((x), __FILE__, __LINE__)
|
|
|
|
static void win_err(const char* file, int line)
|
|
{
|
|
fprintf(stderr, "%s:%d: Win Error %d\n", file, line, (int) GetLastError());
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
exit(EXIT_UNREC);
|
|
}
|
|
#define WIN_ERR() win_err(__FILE__, __LINE__)
|
|
|
|
typedef BOOL (WINAPI* dll_get_class_object_t)(REFCLSID, REFIID, LPVOID*);
|
|
|
|
static void convert_db(const wchar_t* path_from, const wchar_t* path_to)
|
|
{
|
|
HMODULE hm = LoadLibraryA("sqlceca35.dll");
|
|
if (hm == NULL) WIN_ERR();
|
|
dll_get_class_object_t proc = (dll_get_class_object_t)
|
|
GetProcAddress(hm, "DllGetClassObject");
|
|
if (proc == NULL) WIN_ERR();
|
|
|
|
IClassFactory* factory;
|
|
CHECK(proc(&CLSID_Engine, &IID_IClassFactory, (void**) &factory));
|
|
ISSCEEngine* engine;
|
|
CHECK(COM(factory, CreateInstance, NULL, &IID_ISSCEEngine, (void**) &engine));
|
|
COM(factory, Release);
|
|
|
|
wchar_t ci[4096];
|
|
wcscpy(ci+2, L"DataSource=");
|
|
wcscat(ci+2, path_from);
|
|
wcscat(ci+2, L";Max Database Size=4024;Password=" DB_PW);
|
|
DWORD len = 2*wcslen(ci+2);
|
|
memcpy(ci, &len, sizeof(len));
|
|
|
|
wchar_t co[4096];
|
|
wcscpy(co+2, L"DataSource=");
|
|
wcscat(co+2, path_to);
|
|
wcscat(co+2, L";Max Database Size=4024");
|
|
len = 2*wcslen(co+2);
|
|
memcpy(co, &len, sizeof(len));
|
|
|
|
// UpgradeDatabase doesn't seem to do anything
|
|
// CompactDatabase and Repair apparently does the same, but Repair creates
|
|
// a logfile (to chage_ext(data_source, ".log"))
|
|
CHECK(COM(engine, CompactDatabase, ci+2, co+2));
|
|
COM(engine, Release);
|
|
|
|
FreeLibrary(hm);
|
|
}
|
|
|
|
static IDBInitialize* init_oledb(void)
|
|
{
|
|
// shamelessly leak HMODULE, but only load once
|
|
static HMODULE hm = NULL;
|
|
if (hm == NULL) hm = LoadLibraryA("sqlceoledb35.dll");
|
|
if (hm == NULL) WIN_ERR();
|
|
|
|
dll_get_class_object_t proc = (dll_get_class_object_t)
|
|
GetProcAddress(hm, "DllGetClassObject");
|
|
if (proc == NULL) WIN_ERR();
|
|
|
|
IClassFactory* factory;
|
|
CHECK(proc(&CLSID_SQLSERVERCE_3_5, &IID_IClassFactory, (void**) &factory));
|
|
IDBInitialize* init;
|
|
CHECK(COM(factory, CreateInstance, NULL, &IID_IDBInitialize, (void**) &init));
|
|
COM(factory, Release);
|
|
return init;
|
|
}
|
|
|
|
static IDBCreateCommand* open_db(IDBInitialize* init, const wchar_t* fname)
|
|
{
|
|
IDBProperties* props;
|
|
CHECK(COM(init, QueryInterface, &IID_IDBProperties, (void**) &props));
|
|
|
|
// https://docs.microsoft.com/en-us/previous-versions/windows/desktop/automat/bstr
|
|
BSTR fname_dup = SysAllocString(fname);
|
|
|
|
// RAGS connection string:
|
|
// "DataSource=foo.rag;Max Database Size=4024;Password=<that garbage above>"
|
|
DBPROP common_props[] = {
|
|
{ DBPROP_INIT_DATASOURCE, DBPROPOPTIONS_REQUIRED, 0, DB_NULLID,
|
|
{{{VT_BSTR, 0,0,0, {.bstrVal = fname_dup}}}} },
|
|
};
|
|
DBPROP sqlce_props[] = {
|
|
/* { DBPROP_SSCE_DBPASSWORD, DBPROPOPTIONS_REQUIRED, 0, DB_NULLID, */
|
|
/* {{{VT_BSTR, 0,0,0, {.bstrVal = DB_PW}}}} }, */
|
|
{ DBPROP_SSCE_MAX_DATABASE_SIZE, DBPROPOPTIONS_REQUIRED, 0, DB_NULLID,
|
|
{{{VT_I4, 0,0,0, {.intVal = 4024}}}} },
|
|
};
|
|
DBPROPSET propsets[] = {
|
|
{common_props, ALEN(common_props), DBPROPSET_DBINIT},
|
|
{sqlce_props, ALEN(sqlce_props), DBPROPSET_SSCE_DBINIT}
|
|
};
|
|
CHECK(COM(props, SetProperties, ALEN(propsets), propsets));
|
|
SysFreeString(fname_dup);
|
|
COM(props, Release);
|
|
|
|
CHECK(COM(init, Initialize));
|
|
|
|
IDBCreateSession* sess;
|
|
CHECK(COM(init, QueryInterface, &IID_IDBCreateSession, (void**) &sess));
|
|
|
|
IDBCreateCommand* creat;
|
|
CHECK(COM(sess, CreateSession, NULL, &IID_IDBCreateCommand,
|
|
(IUnknown**) &creat));
|
|
COM(sess, Release);
|
|
|
|
return creat;
|
|
}
|
|
|
|
_Static_assert(sizeof(uint8_t) == 1, "wtf?");
|
|
_Static_assert(sizeof(DWORD) == sizeof(uint32_t), "boom");
|
|
_Static_assert(sizeof(wchar_t) == 2, "wchar_t is not windows like");
|
|
typedef struct
|
|
{
|
|
uint32_t length;
|
|
wchar_t data[];
|
|
} bstr_like;
|
|
|
|
#define GEN_SEND(suf, type) \
|
|
static void send_##suf(type d) \
|
|
{ \
|
|
if (fwrite(&d, sizeof(type), 1, stdout) != 1) exit(EXIT_UNREC); \
|
|
}
|
|
|
|
GEN_SEND(u8, uint8_t)
|
|
GEN_SEND(u32, uint32_t)
|
|
|
|
static void send_wstring(const wchar_t* data, uint32_t len)
|
|
{
|
|
if (len == (uint32_t) -1) len = wcslen(data);
|
|
len *= 2;
|
|
if (fwrite(&len, sizeof(uint32_t), 1, stdout) != 1) exit(EXIT_UNREC);
|
|
if (len && fwrite(data, len, 1, stdout) != 1) exit(EXIT_UNREC);
|
|
}
|
|
|
|
static void send_string(const char* data, uint32_t len)
|
|
{
|
|
if (len == (uint32_t) -1) len = strlen(data);
|
|
if (fwrite(&len, sizeof(uint32_t), 1, stdout) != 1) exit(EXIT_UNREC);
|
|
if (len && fwrite(data, len, 1, stdout) != 1) exit(140+EXIT_UNREC);
|
|
}
|
|
|
|
static bool send_hresult(HRESULT hr)
|
|
{
|
|
if (!FAILED(hr)) return false;
|
|
|
|
send_u8(0xff);
|
|
send_u32(hr);
|
|
|
|
IErrorInfo* ei;
|
|
GetErrorInfo(0, &ei);
|
|
if (ei != NULL)
|
|
{
|
|
BSTR str;
|
|
COM(ei, GetDescription, &str);
|
|
if (str)
|
|
{
|
|
send_wstring(str, SysStringLen(str));
|
|
SysFreeString(str);
|
|
}
|
|
else send_wstring(L"", 0);
|
|
COM(ei, Release);
|
|
}
|
|
else
|
|
send_wstring(L"", 0);
|
|
return true;
|
|
}
|
|
#define CRET(hr) do if (send_hresult(hr)) goto end; while (0)
|
|
|
|
static void send_cerror(const char* fmt)
|
|
{
|
|
send_u8(0xfe);
|
|
send_string(fmt, strlen(fmt));
|
|
}
|
|
|
|
// read a string (either char or wchar, doesn't matter...)
|
|
static bstr_like* read_string(void)
|
|
{
|
|
uint32_t l;
|
|
if (fread(&l, sizeof(uint32_t), 1, stdin) != 1) exit(EXIT_COMM);
|
|
bstr_like* ret = (bstr_like*) malloc(sizeof(bstr_like) + l + 2);
|
|
if (ret == NULL) return NULL;
|
|
|
|
ret->length = l;
|
|
if (fread(&ret->data, l, 1, stdin) != 1) { free(ret); return NULL; }
|
|
memset(((char*) ret->data) + l, 0, 2); // actually doesn't have to be wchar
|
|
return ret;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
DBBINDING bind;
|
|
HACCESSOR accessor;
|
|
bool isblob;
|
|
} col_data;
|
|
|
|
static void cmd_exec(IDBCreateCommand* creat)
|
|
{
|
|
#ifdef SINGLE_COMMAND
|
|
bstr_like* cmd = malloc(sizeof(bstr_like) + (wcslen(SINGLE_COMMAND) + 1) * sizeof(DWORD));
|
|
cmd->length = 2*wcslen(SINGLE_COMMAND);
|
|
wcscpy(cmd->data, SINGLE_COMMAND);
|
|
#else
|
|
bstr_like* cmd = read_string();
|
|
if (cmd == NULL) { send_cerror("bad alloc"); return; }
|
|
#endif
|
|
|
|
ICommandText* ct = NULL;
|
|
IRowset* rowset = NULL;
|
|
IColumnsInfo* cii = NULL;
|
|
DBCOLUMNINFO* ci = NULL;
|
|
col_data* cd = NULL;
|
|
IAccessor* accessori = NULL;
|
|
DWORD* row_data = NULL;
|
|
|
|
CRET(COM(creat, CreateCommand, NULL, &IID_ICommandText, (IUnknown**) &ct));
|
|
CRET(COM(ct, SetCommandText, &DBGUID_DEFAULT, cmd->data));
|
|
free(cmd); cmd = NULL;
|
|
|
|
CRET(COM(ct, Execute, NULL, &IID_IRowset, NULL, NULL, (IUnknown**) &rowset));
|
|
if (rowset == NULL)
|
|
{
|
|
send_u8(0);
|
|
goto end;
|
|
}
|
|
|
|
CRET(COM(rowset, QueryInterface, &IID_IColumnsInfo, (void**)&cii));
|
|
|
|
DBORDINAL n_cols;
|
|
OLECHAR* buf;
|
|
CRET(COM(cii, GetColumnInfo, &n_cols, &ci, &buf));
|
|
cd = (col_data*) calloc(n_cols, sizeof(col_data));
|
|
if (cd == NULL) { send_cerror("bad alloc"); goto end; }
|
|
|
|
DBOBJECT blob = { STGM_READ, IID_ISequentialStream };
|
|
|
|
CRET(COM(rowset, QueryInterface, &IID_IAccessor, (void**) &accessori));
|
|
|
|
size_t max_size = sizeof(IUnknown*);
|
|
for (DBORDINAL i = 0; i < n_cols; ++i)
|
|
{
|
|
cd[i].isblob = ci[i].dwFlags & DBCOLUMNFLAGS_ISLONG;
|
|
cd[i].bind.iOrdinal = ci[i].iOrdinal;
|
|
cd[i].bind.obValue = 2 * sizeof(DWORD);
|
|
cd[i].bind.obLength = 0;
|
|
cd[i].bind.obStatus = sizeof(DWORD);
|
|
cd[i].bind.pTypeInfo = NULL;
|
|
cd[i].bind.pObject = cd[i].isblob ? &blob : NULL;
|
|
cd[i].bind.pBindExt = NULL;
|
|
cd[i].bind.dwPart = DBPART_VALUE | DBPART_LENGTH | DBPART_STATUS;
|
|
cd[i].bind.dwMemOwner = DBMEMOWNER_CLIENTOWNED;
|
|
// cd[i].bind.eParamIO ignored
|
|
cd[i].bind.cbMaxLen = cd[i].isblob ? 0 : ci[i].ulColumnSize;
|
|
cd[i].bind.dwFlags = 0;
|
|
cd[i].bind.wType = cd[i].isblob ? DBTYPE_IUNKNOWN : ci[i].wType;
|
|
cd[i].bind.bPrecision = ci[i].bPrecision;
|
|
cd[i].bind.bScale = ci[i].bScale;
|
|
if (!cd[i].isblob && ci[i].ulColumnSize > max_size)
|
|
max_size = ci[i].ulColumnSize;
|
|
|
|
CRET(COM(accessori, CreateAccessor, DBACCESSOR_ROWDATA, 1, &cd[i].bind,
|
|
0, &cd[i].accessor, NULL));
|
|
}
|
|
|
|
send_u8(1);
|
|
send_u32(n_cols);
|
|
for (DBORDINAL i = 0; i < n_cols; ++i)
|
|
{
|
|
send_u32(ci[i].wType);
|
|
send_u32(ci[i].dwFlags);
|
|
send_wstring(ci[i].pwszName, -1);
|
|
}
|
|
|
|
row_data = malloc(2 * sizeof(DWORD) + max_size + 65535);
|
|
if (row_data == NULL) { send_cerror("bad alloc"); goto end; }
|
|
|
|
HROW* row = NULL;
|
|
DBCOUNTITEM got_rows;
|
|
while (COM(rowset, GetNextRows, DB_NULL_HCHAPTER, 0, 1, &got_rows, &row) == S_OK)
|
|
{
|
|
if (got_rows != 1) { send_cerror("invalid got rows"); goto end; }
|
|
|
|
send_u8(1);
|
|
for (DBORDINAL i = 0; i < n_cols; ++i)
|
|
{
|
|
CRET(COM(rowset, GetData, *row, cd[i].accessor, row_data));
|
|
|
|
DWORD len = row_data[0];
|
|
DWORD status = row_data[1];
|
|
if (status == DBSTATUS_S_ISNULL) len = 0;
|
|
|
|
send_u8(cd[i].isblob ? 1 : 0);
|
|
send_u32(status);
|
|
if (cd[i].isblob)
|
|
{
|
|
ISequentialStream* is = *(ISequentialStream**) &row_data[2];
|
|
if (is)
|
|
{
|
|
char buf[4096];
|
|
ULONG act_read;
|
|
do
|
|
{
|
|
CRET(COM(is, Read, buf, ALEN(buf), &act_read));
|
|
if (act_read == 0) break;
|
|
send_u8(1);
|
|
send_string(buf, act_read);
|
|
}
|
|
while (act_read == ALEN(buf));
|
|
send_u8(0);
|
|
COM(is, Release);
|
|
}
|
|
else
|
|
send_cerror("no blob data");
|
|
}
|
|
else
|
|
send_string((char*) &row_data[2], len);
|
|
}
|
|
|
|
COM(rowset, ReleaseRows, got_rows, row, NULL, NULL, NULL);
|
|
}
|
|
|
|
send_u8(0);
|
|
|
|
end:
|
|
free(row_data);
|
|
if (cd)
|
|
{
|
|
for (DBORDINAL i = 0; i < n_cols; ++i)
|
|
if (cd[i].accessor) COM(accessori, ReleaseAccessor, cd[i].accessor, NULL);
|
|
free(cd);
|
|
}
|
|
if (accessori) COM(accessori, Release);
|
|
if (ci) CoTaskMemFree(ci);
|
|
if (cii) COM(cii, Release);
|
|
if (rowset) COM(rowset, Release);
|
|
if (ct) COM(ct, Release);
|
|
free(cmd);
|
|
}
|
|
|
|
static wchar_t path[MAX_PATH];
|
|
void clean_path(void) { DeleteFileW(path); }
|
|
|
|
int wmain(int argc, wchar_t** argv, wchar_t** x)
|
|
{
|
|
if (argc != 2)
|
|
{
|
|
fprintf(stderr, "Usage: %ls db_name\n", argv[0]);
|
|
return EXIT_USER;
|
|
}
|
|
setmode(fileno(stdin), O_BINARY);
|
|
setmode(fileno(stdout), O_BINARY);
|
|
|
|
CHECK(CoInitializeEx(NULL, COINIT_MULTITHREADED));
|
|
// A rant about creating a tmp file. _wtmpnam is buggy in wine, works
|
|
// differently if you have microsoft's version of msvcrt installed, so avoid
|
|
// it. GetTempFileName creates the file, and sqlce pukes if the target already
|
|
// exists. Plus they both depend on creating the target because otherwise
|
|
// there's no way to check that the name is really unique. So just give up and
|
|
// create a random string.
|
|
{
|
|
const int RAND_LEN = 32;
|
|
if (!GetTempPathW(MAX_PATH - RAND_LEN - 1, path)) WIN_ERR();
|
|
wchar_t* p = path + wcslen(path);
|
|
if (!SystemFunction036(p, RAND_LEN * sizeof(wchar_t))) WIN_ERR();
|
|
// generate a random string consisting 'a'..'z', '0'..'9'
|
|
for (int i = 0; i < RAND_LEN; ++i)
|
|
p[i] = "a\x16"[(p[i] % 36) >= 26] + (p[i] % 36);
|
|
// trailing \0 is there due to static initialization
|
|
atexit(clean_path);
|
|
}
|
|
|
|
convert_db(argv[1], path);
|
|
|
|
IDBInitialize* init = init_oledb();
|
|
IDBCreateCommand* creat = open_db(init, path);
|
|
|
|
#ifdef SINGLE_COMMAND
|
|
cmd_exec(creat);
|
|
#else
|
|
while (true)
|
|
{
|
|
fflush(stdout);
|
|
uint8_t command;
|
|
if (fread(&command, sizeof(command), 1, stdin) != 1) break;
|
|
switch (command)
|
|
{
|
|
case 0:
|
|
cmd_exec(creat);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
fflush(stdout);
|
|
COM(creat, Release);
|
|
COM(init, Release);
|
|
return EXIT_OK;
|
|
}
|