scraps

Abandon all hope, ye who enter here.
git clone https://git.neptards.moe/neptards/scraps.git
Log | Files | Refs | Submodules | README | LICENSE

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 }