load.c (15070B)
1 // Run an sql command without any input. Useful if you want to use wine debugger 2 // where it's impossible to pipe in a file/program to stdin. 3 4 // #define SINGLE_COMMAND L"select TABLE_NAME from INFORMATION_SCHEMA.TABLES;" 5 // #define SINGLE_COMMAND L"select * from GameData;" 6 7 #define __USE_MINGW_ANSI_STDIO 0 8 #include <stdio.h> 9 #include <wchar.h> 10 #include <stdbool.h> 11 #include <stdint.h> 12 13 // setmode 14 #include <fcntl.h> 15 #include <io.h> 16 17 #define DBINITCONSTANTS 18 #define WIN32_LEAN_AND_MEAN 19 #ifndef UNICODE 20 #define UNICODE 21 #endif 22 #include <windows.h> 23 #include <objbase.h> 24 #include <guiddef.h> 25 #include <oledb.h> 26 27 extern BOOLEAN NTAPI SystemFunction036(PVOID, ULONG); 28 29 static const GUID CLSID_SQLSERVERCE_3_5 = 30 {0xf49c559d, 0xe9e5, 0x467c, {0x8c, 0x18, 0x33, 0x26, 0xaa, 0xe4, 0xeb, 0xcc}}; 31 static const GUID CLSID_Engine = 32 {0xa9d3060d, 0x3526, 0x4538, {0xb1, 0x3a, 0x19, 0x13, 0x56, 0x8d, 0xaa, 0x0d}}; 33 static const GUID IID_ISSCEEngine = 34 {0x10ec3e45,0x0870,0x4d7b, {0x9a,0x2d,0xf4,0xf8,0x1b,0x6b,0x7f,0xa2}}; 35 36 static const GUID DBPROPSET_SSCE_DBINIT = 37 {0x2b9ab5ba, 0x4f6c, 0x4ddd, {0xbf, 0x18, 0x24, 0xdd, 0x4b, 0xd4, 0x18, 0x48}}; 38 39 #define DBPROP_SSCE_DBPASSWORD 0x1FBL 40 #define DBPROP_SSCE_MAX_DATABASE_SIZE 0x20BL 41 42 #ifdef __WINE_WINDOWS_H 43 static const DBID DB_NULLID = {{{0x00000000L,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}}, 0, {(LPOLESTR) NULL}}; 44 # ifndef __IAccessor_INTERFACE_DEFINED__ 45 enum DBACCESSORFLAGSENUM 46 { 47 DBACCESSOR_INVALID = 0, 48 DBACCESSOR_PASSBYREF = 0x1, 49 DBACCESSOR_ROWDATA = 0x2, 50 DBACCESSOR_PARAMETERDATA = 0x4, 51 DBACCESSOR_OPTIMIZED = 0x8, 52 DBACCESSOR_INHERITED = 0x10 53 }; 54 # endif 55 #endif 56 57 typedef interface ISSCEEngine ISSCEEngine; 58 interface ISSCEEngine 59 { 60 CONST_VTBL struct ISSCEEngineVtbl *lpVtbl; 61 }; 62 63 typedef enum REPAIROPTION { DELETECORRUPTED, RECOVERCORRUPTED } REPAIROPTION; 64 typedef struct ISSCEEngineVtbl 65 { 66 BEGIN_INTERFACE 67 68 HRESULT (STDMETHODCALLTYPE *QueryInterface)(ISSCEEngine* thiz, REFIID riid, void **obj); 69 ULONG (STDMETHODCALLTYPE *AddRef)(ISSCEEngine* This); 70 ULONG (STDMETHODCALLTYPE *Release)(ISSCEEngine* This); 71 72 HRESULT (STDMETHODCALLTYPE *CompactDatabase)( 73 ISSCEEngine* thiz, BSTR src_conn, BSTR dst_conn); 74 75 HRESULT (STDMETHODCALLTYPE *get_ErrorRecords)( 76 ISSCEEngine* thiz, /*ISSCEErrors*/ void **val); 77 78 HRESULT (STDMETHODCALLTYPE *CreateDatabase)(ISSCEEngine* thiz, BSTR conn); 79 80 HRESULT (STDMETHODCALLTYPE *Repair)( 81 ISSCEEngine* thiz, BSTR src, BSTR dst, REPAIROPTION opt); 82 83 // doesn't do anything on wine? 84 HRESULT (STDMETHODCALLTYPE *UpgradeDatabase)( 85 ISSCEEngine* thiz, BSTR src, BSTR dst); 86 87 END_INTERFACE 88 } ISSCEEngineVtbl; 89 90 #define DB_PW \ 91 L"\u00C7\u00B0\u00A5\u00E0\u00F2\u00C5\u00C5\u00C7\u00C9\u00E0\u00F1\u00F1\u00F8" 92 #define COM(x, fun, ...) ((x)->lpVtbl->fun((x), ##__VA_ARGS__)) 93 #define ALEN(x) (sizeof(x) / sizeof(x[0])) 94 95 #define EXIT_OK 0 96 #define EXIT_USER 1 // invalid cmd line usage 97 #define EXIT_COMM 2 // read invalid data on stdin 98 #define EXIT_UNREC 3 // unrecoverable error 99 100 static void check(HRESULT hr, const char* file, int line) 101 { 102 if (!FAILED(hr)) return; 103 104 IErrorInfo* ei; 105 GetErrorInfo(0, &ei); 106 if (ei != NULL) 107 { 108 BSTR str; 109 COM(ei, GetDescription, &str); 110 fprintf(stderr, "%s:%d: HRESULT %x: %ls\n", file, line, (unsigned) hr, str); 111 SysFreeString(str); 112 COM(ei, Release); 113 } 114 else 115 fprintf(stderr, "%s:%d: HRESULT %x\n", file, line, (unsigned) hr); 116 117 fflush(stdout); 118 fflush(stderr); 119 exit(EXIT_UNREC); 120 } 121 #define CHECK(x) check((x), __FILE__, __LINE__) 122 123 static void win_err(const char* file, int line) 124 { 125 fprintf(stderr, "%s:%d: Win Error %d\n", file, line, (int) GetLastError()); 126 fflush(stdout); 127 fflush(stderr); 128 exit(EXIT_UNREC); 129 } 130 #define WIN_ERR() win_err(__FILE__, __LINE__) 131 132 typedef BOOL (WINAPI* dll_get_class_object_t)(REFCLSID, REFIID, LPVOID*); 133 134 static void convert_db(const wchar_t* path_from, const wchar_t* path_to) 135 { 136 HMODULE hm = LoadLibraryA("sqlceca35.dll"); 137 if (hm == NULL) WIN_ERR(); 138 dll_get_class_object_t proc = (dll_get_class_object_t) 139 GetProcAddress(hm, "DllGetClassObject"); 140 if (proc == NULL) WIN_ERR(); 141 142 IClassFactory* factory; 143 CHECK(proc(&CLSID_Engine, &IID_IClassFactory, (void**) &factory)); 144 ISSCEEngine* engine; 145 CHECK(COM(factory, CreateInstance, NULL, &IID_ISSCEEngine, (void**) &engine)); 146 COM(factory, Release); 147 148 wchar_t ci[4096]; 149 wcscpy(ci+2, L"DataSource="); 150 wcscat(ci+2, path_from); 151 wcscat(ci+2, L";Max Database Size=4024;Password=" DB_PW); 152 DWORD len = 2*wcslen(ci+2); 153 memcpy(ci, &len, sizeof(len)); 154 155 wchar_t co[4096]; 156 wcscpy(co+2, L"DataSource="); 157 wcscat(co+2, path_to); 158 wcscat(co+2, L";Max Database Size=4024"); 159 len = 2*wcslen(co+2); 160 memcpy(co, &len, sizeof(len)); 161 162 // UpgradeDatabase doesn't seem to do anything 163 // CompactDatabase and Repair apparently does the same, but Repair creates 164 // a logfile (to chage_ext(data_source, ".log")) 165 CHECK(COM(engine, CompactDatabase, ci+2, co+2)); 166 COM(engine, Release); 167 168 FreeLibrary(hm); 169 } 170 171 static IDBInitialize* init_oledb(void) 172 { 173 // shamelessly leak HMODULE, but only load once 174 static HMODULE hm = NULL; 175 if (hm == NULL) hm = LoadLibraryA("sqlceoledb35.dll"); 176 if (hm == NULL) WIN_ERR(); 177 178 dll_get_class_object_t proc = (dll_get_class_object_t) 179 GetProcAddress(hm, "DllGetClassObject"); 180 if (proc == NULL) WIN_ERR(); 181 182 IClassFactory* factory; 183 CHECK(proc(&CLSID_SQLSERVERCE_3_5, &IID_IClassFactory, (void**) &factory)); 184 IDBInitialize* init; 185 CHECK(COM(factory, CreateInstance, NULL, &IID_IDBInitialize, (void**) &init)); 186 COM(factory, Release); 187 return init; 188 } 189 190 static IDBCreateCommand* open_db(IDBInitialize* init, const wchar_t* fname) 191 { 192 IDBProperties* props; 193 CHECK(COM(init, QueryInterface, &IID_IDBProperties, (void**) &props)); 194 195 // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/automat/bstr 196 BSTR fname_dup = SysAllocString(fname); 197 198 // RAGS connection string: 199 // "DataSource=foo.rag;Max Database Size=4024;Password=<that garbage above>" 200 DBPROP common_props[] = { 201 { DBPROP_INIT_DATASOURCE, DBPROPOPTIONS_REQUIRED, 0, DB_NULLID, 202 {{{VT_BSTR, 0,0,0, {.bstrVal = fname_dup}}}} }, 203 }; 204 DBPROP sqlce_props[] = { 205 /* { DBPROP_SSCE_DBPASSWORD, DBPROPOPTIONS_REQUIRED, 0, DB_NULLID, */ 206 /* {{{VT_BSTR, 0,0,0, {.bstrVal = DB_PW}}}} }, */ 207 { DBPROP_SSCE_MAX_DATABASE_SIZE, DBPROPOPTIONS_REQUIRED, 0, DB_NULLID, 208 {{{VT_I4, 0,0,0, {.intVal = 4024}}}} }, 209 }; 210 DBPROPSET propsets[] = { 211 {common_props, ALEN(common_props), DBPROPSET_DBINIT}, 212 {sqlce_props, ALEN(sqlce_props), DBPROPSET_SSCE_DBINIT} 213 }; 214 CHECK(COM(props, SetProperties, ALEN(propsets), propsets)); 215 SysFreeString(fname_dup); 216 COM(props, Release); 217 218 CHECK(COM(init, Initialize)); 219 220 IDBCreateSession* sess; 221 CHECK(COM(init, QueryInterface, &IID_IDBCreateSession, (void**) &sess)); 222 223 IDBCreateCommand* creat; 224 CHECK(COM(sess, CreateSession, NULL, &IID_IDBCreateCommand, 225 (IUnknown**) &creat)); 226 COM(sess, Release); 227 228 return creat; 229 } 230 231 _Static_assert(sizeof(uint8_t) == 1, "wtf?"); 232 _Static_assert(sizeof(DWORD) == sizeof(uint32_t), "boom"); 233 _Static_assert(sizeof(wchar_t) == 2, "wchar_t is not windows like"); 234 typedef struct 235 { 236 uint32_t length; 237 wchar_t data[]; 238 } bstr_like; 239 240 #define GEN_SEND(suf, type) \ 241 static void send_##suf(type d) \ 242 { \ 243 if (fwrite(&d, sizeof(type), 1, stdout) != 1) exit(EXIT_UNREC); \ 244 } 245 246 GEN_SEND(u8, uint8_t) 247 GEN_SEND(u32, uint32_t) 248 249 static void send_wstring(const wchar_t* data, uint32_t len) 250 { 251 if (len == (uint32_t) -1) len = wcslen(data); 252 len *= 2; 253 if (fwrite(&len, sizeof(uint32_t), 1, stdout) != 1) exit(EXIT_UNREC); 254 if (len && fwrite(data, len, 1, stdout) != 1) exit(EXIT_UNREC); 255 } 256 257 static void send_string(const char* data, uint32_t len) 258 { 259 if (len == (uint32_t) -1) len = strlen(data); 260 if (fwrite(&len, sizeof(uint32_t), 1, stdout) != 1) exit(EXIT_UNREC); 261 if (len && fwrite(data, len, 1, stdout) != 1) exit(140+EXIT_UNREC); 262 } 263 264 static bool send_hresult(HRESULT hr) 265 { 266 if (!FAILED(hr)) return false; 267 268 send_u8(0xff); 269 send_u32(hr); 270 271 IErrorInfo* ei; 272 GetErrorInfo(0, &ei); 273 if (ei != NULL) 274 { 275 BSTR str; 276 COM(ei, GetDescription, &str); 277 if (str) 278 { 279 send_wstring(str, SysStringLen(str)); 280 SysFreeString(str); 281 } 282 else send_wstring(L"", 0); 283 COM(ei, Release); 284 } 285 else 286 send_wstring(L"", 0); 287 return true; 288 } 289 #define CRET(hr) do if (send_hresult(hr)) goto end; while (0) 290 291 static void send_cerror(const char* fmt) 292 { 293 send_u8(0xfe); 294 send_string(fmt, strlen(fmt)); 295 } 296 297 // read a string (either char or wchar, doesn't matter...) 298 static bstr_like* read_string(void) 299 { 300 uint32_t l; 301 if (fread(&l, sizeof(uint32_t), 1, stdin) != 1) exit(EXIT_COMM); 302 bstr_like* ret = (bstr_like*) malloc(sizeof(bstr_like) + l + 2); 303 if (ret == NULL) return NULL; 304 305 ret->length = l; 306 if (fread(&ret->data, l, 1, stdin) != 1) { free(ret); return NULL; } 307 memset(((char*) ret->data) + l, 0, 2); // actually doesn't have to be wchar 308 return ret; 309 } 310 311 typedef struct 312 { 313 DBBINDING bind; 314 HACCESSOR accessor; 315 bool isblob; 316 } col_data; 317 318 static void cmd_exec(IDBCreateCommand* creat) 319 { 320 #ifdef SINGLE_COMMAND 321 bstr_like* cmd = malloc(sizeof(bstr_like) + (wcslen(SINGLE_COMMAND) + 1) * sizeof(DWORD)); 322 cmd->length = 2*wcslen(SINGLE_COMMAND); 323 wcscpy(cmd->data, SINGLE_COMMAND); 324 #else 325 bstr_like* cmd = read_string(); 326 if (cmd == NULL) { send_cerror("bad alloc"); return; } 327 #endif 328 329 ICommandText* ct = NULL; 330 IRowset* rowset = NULL; 331 IColumnsInfo* cii = NULL; 332 DBCOLUMNINFO* ci = NULL; 333 col_data* cd = NULL; 334 IAccessor* accessori = NULL; 335 DWORD* row_data = NULL; 336 337 CRET(COM(creat, CreateCommand, NULL, &IID_ICommandText, (IUnknown**) &ct)); 338 CRET(COM(ct, SetCommandText, &DBGUID_DEFAULT, cmd->data)); 339 free(cmd); cmd = NULL; 340 341 CRET(COM(ct, Execute, NULL, &IID_IRowset, NULL, NULL, (IUnknown**) &rowset)); 342 if (rowset == NULL) 343 { 344 send_u8(0); 345 goto end; 346 } 347 348 CRET(COM(rowset, QueryInterface, &IID_IColumnsInfo, (void**)&cii)); 349 350 DBORDINAL n_cols; 351 OLECHAR* buf; 352 CRET(COM(cii, GetColumnInfo, &n_cols, &ci, &buf)); 353 cd = (col_data*) calloc(n_cols, sizeof(col_data)); 354 if (cd == NULL) { send_cerror("bad alloc"); goto end; } 355 356 DBOBJECT blob = { STGM_READ, IID_ISequentialStream }; 357 358 CRET(COM(rowset, QueryInterface, &IID_IAccessor, (void**) &accessori)); 359 360 size_t max_size = sizeof(IUnknown*); 361 for (DBORDINAL i = 0; i < n_cols; ++i) 362 { 363 cd[i].isblob = ci[i].dwFlags & DBCOLUMNFLAGS_ISLONG; 364 cd[i].bind.iOrdinal = ci[i].iOrdinal; 365 cd[i].bind.obValue = 2 * sizeof(DWORD); 366 cd[i].bind.obLength = 0; 367 cd[i].bind.obStatus = sizeof(DWORD); 368 cd[i].bind.pTypeInfo = NULL; 369 cd[i].bind.pObject = cd[i].isblob ? &blob : NULL; 370 cd[i].bind.pBindExt = NULL; 371 cd[i].bind.dwPart = DBPART_VALUE | DBPART_LENGTH | DBPART_STATUS; 372 cd[i].bind.dwMemOwner = DBMEMOWNER_CLIENTOWNED; 373 // cd[i].bind.eParamIO ignored 374 cd[i].bind.cbMaxLen = cd[i].isblob ? 0 : ci[i].ulColumnSize; 375 cd[i].bind.dwFlags = 0; 376 cd[i].bind.wType = cd[i].isblob ? DBTYPE_IUNKNOWN : ci[i].wType; 377 cd[i].bind.bPrecision = ci[i].bPrecision; 378 cd[i].bind.bScale = ci[i].bScale; 379 if (!cd[i].isblob && ci[i].ulColumnSize > max_size) 380 max_size = ci[i].ulColumnSize; 381 382 CRET(COM(accessori, CreateAccessor, DBACCESSOR_ROWDATA, 1, &cd[i].bind, 383 0, &cd[i].accessor, NULL)); 384 } 385 386 send_u8(1); 387 send_u32(n_cols); 388 for (DBORDINAL i = 0; i < n_cols; ++i) 389 { 390 send_u32(ci[i].wType); 391 send_u32(ci[i].dwFlags); 392 send_wstring(ci[i].pwszName, -1); 393 } 394 395 row_data = malloc(2 * sizeof(DWORD) + max_size + 65535); 396 if (row_data == NULL) { send_cerror("bad alloc"); goto end; } 397 398 HROW* row = NULL; 399 DBCOUNTITEM got_rows; 400 while (COM(rowset, GetNextRows, DB_NULL_HCHAPTER, 0, 1, &got_rows, &row) == S_OK) 401 { 402 if (got_rows != 1) { send_cerror("invalid got rows"); goto end; } 403 404 send_u8(1); 405 for (DBORDINAL i = 0; i < n_cols; ++i) 406 { 407 CRET(COM(rowset, GetData, *row, cd[i].accessor, row_data)); 408 409 DWORD len = row_data[0]; 410 DWORD status = row_data[1]; 411 if (status == DBSTATUS_S_ISNULL) len = 0; 412 413 send_u8(cd[i].isblob ? 1 : 0); 414 send_u32(status); 415 if (cd[i].isblob) 416 { 417 ISequentialStream* is = *(ISequentialStream**) &row_data[2]; 418 if (is) 419 { 420 char buf[4096]; 421 ULONG act_read; 422 do 423 { 424 CRET(COM(is, Read, buf, ALEN(buf), &act_read)); 425 if (act_read == 0) break; 426 send_u8(1); 427 send_string(buf, act_read); 428 } 429 while (act_read == ALEN(buf)); 430 send_u8(0); 431 COM(is, Release); 432 } 433 else 434 send_cerror("no blob data"); 435 } 436 else 437 send_string((char*) &row_data[2], len); 438 } 439 440 COM(rowset, ReleaseRows, got_rows, row, NULL, NULL, NULL); 441 } 442 443 send_u8(0); 444 445 end: 446 free(row_data); 447 if (cd) 448 { 449 for (DBORDINAL i = 0; i < n_cols; ++i) 450 if (cd[i].accessor) COM(accessori, ReleaseAccessor, cd[i].accessor, NULL); 451 free(cd); 452 } 453 if (accessori) COM(accessori, Release); 454 if (ci) CoTaskMemFree(ci); 455 if (cii) COM(cii, Release); 456 if (rowset) COM(rowset, Release); 457 if (ct) COM(ct, Release); 458 free(cmd); 459 } 460 461 static wchar_t path[MAX_PATH]; 462 void clean_path(void) { DeleteFileW(path); } 463 464 int wmain(int argc, wchar_t** argv, wchar_t** x) 465 { 466 if (argc != 2) 467 { 468 fprintf(stderr, "Usage: %ls db_name\n", argv[0]); 469 return EXIT_USER; 470 } 471 setmode(fileno(stdin), O_BINARY); 472 setmode(fileno(stdout), O_BINARY); 473 474 CHECK(CoInitializeEx(NULL, COINIT_MULTITHREADED)); 475 // A rant about creating a tmp file. _wtmpnam is buggy in wine, works 476 // differently if you have microsoft's version of msvcrt installed, so avoid 477 // it. GetTempFileName creates the file, and sqlce pukes if the target already 478 // exists. Plus they both depend on creating the target because otherwise 479 // there's no way to check that the name is really unique. So just give up and 480 // create a random string. 481 { 482 const int RAND_LEN = 32; 483 if (!GetTempPathW(MAX_PATH - RAND_LEN - 1, path)) WIN_ERR(); 484 wchar_t* p = path + wcslen(path); 485 if (!SystemFunction036(p, RAND_LEN * sizeof(wchar_t))) WIN_ERR(); 486 // generate a random string consisting 'a'..'z', '0'..'9' 487 for (int i = 0; i < RAND_LEN; ++i) 488 p[i] = "a\x16"[(p[i] % 36) >= 26] + (p[i] % 36); 489 // trailing \0 is there due to static initialization 490 atexit(clean_path); 491 } 492 493 convert_db(argv[1], path); 494 495 IDBInitialize* init = init_oledb(); 496 IDBCreateCommand* creat = open_db(init, path); 497 498 #ifdef SINGLE_COMMAND 499 cmd_exec(creat); 500 #else 501 while (true) 502 { 503 fflush(stdout); 504 uint8_t command; 505 if (fread(&command, sizeof(command), 1, stdin) != 1) break; 506 switch (command) 507 { 508 case 0: 509 cmd_exec(creat); 510 break; 511 512 default: 513 break; 514 } 515 } 516 #endif 517 518 fflush(stdout); 519 COM(creat, Release); 520 COM(init, Release); 521 return EXIT_OK; 522 }