"Fossies" - the Fresh Open Source Software Archive

Member "AutoHotkey_L-1.1.33.09/source/TextIO.cpp" (8 May 2021, 38432 Bytes) of package /windows/misc/AutoHotkey_L-1.1.33.09.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "TextIO.cpp" see the Fossies "Dox" file reference documentation.

    1 #include "stdafx.h"
    2 #include "TextIO.h"
    3 #include "script.h"
    4 #include "script_object.h"
    5 
    6 UINT g_ACP = GetACP(); // Requires a reboot to change.
    7 #define INVALID_CHAR UorA(0xFFFD, '?')
    8 
    9 #ifndef UNICODE
   10 
   11 CPINFO GetACPInfo()
   12 {
   13     CPINFO info;
   14     GetCPInfo(CP_ACP, &info);
   15     return info;
   16 }
   17 CPINFO g_ACPInfo = GetACPInfo();
   18 
   19 // Benchmarks faster than _ismbblead_l with ACP locale:
   20 bool IsLeadByteACP(BYTE b)
   21 {
   22     // Benchmarks slightly faster without this check, even when MaxCharSize == 1:
   23     //if (g_ACPInfo.MaxCharSize > 1)
   24     for (int i = 0; i < _countof(g_ACPInfo.LeadByte) && g_ACPInfo.LeadByte[i]; i += 2)
   25         if (b >= g_ACPInfo.LeadByte[i] && b <= g_ACPInfo.LeadByte[i+1])
   26             return true;
   27     return false;
   28 }
   29 
   30 #endif
   31 
   32 
   33 
   34 //
   35 // TextStream
   36 //
   37 bool TextStream::Open(LPCTSTR aFileSpec, DWORD aFlags, UINT aCodePage)
   38 {
   39     mLength = 0; // Set the default value here so _Open() can change it.
   40     if (!_Open(aFileSpec, aFlags))
   41         return false;
   42 
   43     SetCodePage(aCodePage);
   44     mFlags = aFlags;
   45     mLastWriteChar = 0;
   46 
   47     int mode = aFlags & ACCESS_MODE_MASK;
   48     if (mode == USEHANDLE)
   49         return true;
   50     if (mode != TextStream::WRITE) {
   51         // Detect UTF-8 and UTF-16LE BOMs
   52         if (mLength < 3)
   53             Read(TEXT_IO_BLOCK); // TEXT_IO_BLOCK vs 3 for consistency and average-case performance.
   54         mPos = mBuffer;
   55         if (mLength >= 2) {
   56             if (mBuffer[0] == 0xFF && mBuffer[1] == 0xFE) {
   57                 mPosW += 1;
   58                 SetCodePage(CP_UTF16);
   59             }
   60             else if (mBuffer[0] == 0xEF && mBuffer[1] == 0xBB) {
   61                 if (mLength >= 3 && mBuffer[2] == 0xBF) {
   62                     mPosA += 3;
   63                     SetCodePage(CP_UTF8);
   64                 }
   65             }
   66         }
   67     }
   68     if (mode == TextStream::WRITE || (mode == TextStream::APPEND || mode == TextStream::UPDATE) && _Length() == 0) {
   69         if (aFlags & BOM_UTF8)
   70             _Write("\xEF\xBB\xBF", 3);
   71         else if (aFlags & BOM_UTF16)
   72             _Write("\xFF\xFE", 2);
   73     }
   74     else if (mode == TextStream::APPEND)
   75     {
   76         mPos = NULL; // Without this, RollbackFilePointer() gets called later on and
   77         mLength = 0; // if the file had no UTF-8 BOM we end up in the wrong position.
   78         _Seek(0, SEEK_END);
   79     }
   80 
   81     return true;
   82 }
   83 
   84 
   85 
   86 DWORD TextStream::Read(LPTSTR aBuf, DWORD aBufLen, int aNumLines)
   87 {
   88     if (!PrepareToRead())
   89         return 0;
   90 
   91     DWORD target_used = 0;
   92     LPBYTE src, src_end;
   93     TCHAR dst[UorA(2,4)];
   94     int src_size; // Size of source character, in bytes.
   95     int dst_size; // Number of code units in destination character.
   96 
   97     UINT codepage = mCodePage; // For performance.
   98     int chr_size = (codepage == CP_UTF16) ? sizeof(WCHAR) : sizeof(CHAR);
   99 
  100     // This is set each iteration based on how many bytes we *need* to have in the buffer.
  101     // Avoid setting it higher than necessary since that can cause undesired effects with
  102     // non-file handles -  such as a console waiting for a second line of input when the
  103     // first line is waiting in our buffer.
  104     int next_size = chr_size;
  105 
  106     while (target_used < aBufLen)
  107     {
  108         // Ensure the buffer contains at least one CHAR/WCHAR, or all bytes of the next
  109         // character as determined by a previous iteration of the loop.  Note that Read()
  110         // only occurs if the buffer contains less than next_size bytes, and that this
  111         // check does not occur frequently due to buffering and the inner loop below.
  112         if (!ReadAtLeast(next_size) && !mLength)
  113             break;
  114 
  115         // Reset to default (see comments above).
  116         next_size = chr_size;
  117 
  118         // The following macro is used when there is insufficient data in the buffer,
  119         // to determine if more data can possibly be read in.  Using mLastRead should be
  120         // faster than AtEOF(), and more reliable with console/pipe handles.
  121         #define LAST_READ_HIT_EOF (mLastRead == 0)
  122         
  123         // Because we copy mPos into a temporary variable here and update mPos at the end of
  124         // each outer loop iteration, it is very important that ReadAtLeast() not be called
  125         // after this point.
  126         src = mPos;
  127         src_end = mBuffer + mLength; // Maint: mLength is in bytes.
  128         
  129         // Ensure there are an even number of bytes in the buffer if we are reading UTF-16.
  130         // This can happen (for instance) when dealing with binary files which also contain
  131         // UTF-16 strings, or if a UTF-16 file is missing its last byte.
  132         if (codepage == CP_UTF16 && ((src_end - src) & 1))
  133         {
  134             // Try to defer processing of the odd byte until the next byte is read.
  135             --src_end;
  136             // If it's the only byte remaining, the safest thing to do is probably to drop it
  137             // from the stream and output an invalid char so that the error can be detected:
  138             if (src_end == src)
  139             {
  140                 mPos = NULL;
  141                 mLength = 0;
  142                 aBuf[target_used++] = INVALID_CHAR;
  143                 break;
  144             }
  145         }
  146 
  147         for ( ; src < src_end && target_used < aBufLen; src += src_size)
  148         {
  149             if (codepage == CP_UTF16)
  150             {
  151                 src_size = sizeof(WCHAR); // Set default (currently never overridden).
  152                 LPWSTR cp = (LPWSTR)src;
  153                 if (*cp == '\r')
  154                 {
  155                     if (cp + 2 <= (LPWSTR)src_end)
  156                     {
  157                         if (cp[1] == '\n')
  158                         {
  159                             // There's an \n following this \r, but is \r\n considered EOL?
  160                             if ( !(mFlags & EOL_CRLF) )
  161                                 // This \r isn't being translated, so just write it out.
  162                                 aBuf[target_used++] = '\r';
  163                             continue;
  164                         }
  165                     }
  166                     else if (!LAST_READ_HIT_EOF)
  167                     {
  168                         // There's not enough data in the buffer to determine if this is \r\n.
  169                         // Let the next iteration handle this char after reading more data.
  170                         next_size = 2 * sizeof(WCHAR);
  171                         break;
  172                     }
  173                     // Since above didn't break or continue, this is an orphan \r.
  174                 }
  175                 // There doesn't seem to be much need to give surrogate pairs special handling,
  176                 // so the following is disabled for now.  Some "brute force" tests on Windows 7
  177                 // showed that none of the ANSI code pages are capable of representing any of
  178                 // the supplementary characters.  Even if we pass the full pair in a single call,
  179                 // the result is the same as with two separate calls: "??".
  180                 /*if (*cp >= 0xD800 && *cp <= 0xDBFF) // High surrogate.
  181                 {
  182                     if (src + 3 >= src_end && !LAST_READ_HIT_EOF)
  183                     {
  184                         // There should be a low surrogate following this, but since there's
  185                         // not enough data in the buffer we need to postpone processing it.
  186                         break;
  187                     }
  188                     // Rather than discarding unpaired high/low surrogate code units, let them
  189                     // through as though this is UCS-2, not UTF-16. The following check is not
  190                     // necessary since low surrogates can't be misinterpreted as \r or \n:
  191                     //if (cp[1] >= 0xDC00 && cp[1] <= 0xDFFF)
  192                 }*/
  193 #ifdef UNICODE
  194                 *dst = *cp;
  195                 dst_size = 1;
  196 #else
  197                 dst_size = WideCharToMultiByte(CP_ACP, 0, cp, 1, dst, _countof(dst), NULL, NULL);
  198 #endif
  199             }
  200             else
  201             {
  202                 src_size = 1; // Set default.
  203                 if (*src < 0x80)
  204                 {
  205                     if (*src == '\r')
  206                     {
  207                         if (src + 1 < src_end)
  208                         {
  209                             if (src[1] == '\n')
  210                             {
  211                                 // There's an \n following this \r, but is \r\n considered EOL?
  212                                 if ( !(mFlags & EOL_CRLF) )
  213                                     // This \r isn't being translated, so just write it out.
  214                                     aBuf[target_used++] = '\r';
  215                                 continue;
  216                             }
  217                         }
  218                         else if (!LAST_READ_HIT_EOF)
  219                         {
  220                             // There's not enough data in the buffer to determine if this is \r\n.
  221                             // Let the next iteration handle this char after reading more data.
  222                             next_size = 2;
  223                             break;
  224                         }
  225                         // Since above didn't break or continue, this is an orphan \r.
  226                     }
  227                     // No conversion needed for ASCII chars.
  228                     *dst = *(LPSTR)src;
  229                     dst_size = 1;
  230                 }
  231                 else
  232                 {
  233                     if (codepage == CP_UTF8)
  234                     {
  235                         if ((*src & 0xE0) == 0xC0)
  236                             src_size = 2;
  237                         else if ((*src & 0xF0) == 0xE0)
  238                             src_size = 3;
  239                         else if ((*src & 0xF8) == 0xF0)
  240                             src_size = 4;
  241                         else { // Invalid in current UTF-8 standard.
  242                             aBuf[target_used++] = INVALID_CHAR;
  243                             continue;
  244                         }
  245                     }
  246                     else if (IsLeadByte(*src))
  247                         src_size = 2;
  248                     // Otherwise, leave it at the default set above: 1.
  249                     
  250                     // Ensure that the expected number of bytes are available:
  251                     if (src + src_size > src_end)
  252                     {
  253                         if (LAST_READ_HIT_EOF)
  254                         {
  255                             mLength = 0; // Discard all remaining data, since it appears to be invalid.
  256                             src = NULL;  // mPos is set to this outside the inner loop.
  257                             aBuf[target_used++] = INVALID_CHAR;
  258                         }
  259                         else
  260                         {
  261                             next_size = src_size;
  262                             // Let the next iteration handle this char after reading more data.
  263                             // If no more data is read, LAST_READ_HIT_EOF will be true and the
  264                             // next iteration will produce INVALID_CHAR.
  265                         }
  266                         break;
  267                     }
  268 #ifdef UNICODE
  269                     dst_size = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, (LPSTR)src, src_size, dst, _countof(dst));
  270 #else
  271                     if (codepage == g_ACP)
  272                     {
  273                         // This char doesn't require any conversion.
  274                         *dst = *(LPSTR)src;
  275                         if (src_size > 1) // Can only be 1 or 2 in this case.
  276                             dst[1] = src[1];
  277                         dst_size = src_size;
  278                     }
  279                     else
  280                     {
  281                         // Convert this single- or multi-byte char to Unicode.
  282                         int wide_size;
  283                         WCHAR wide_char[2];
  284                         wide_size = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, (LPSTR)src, src_size, wide_char, _countof(wide_char));
  285                         if (wide_size)
  286                         {
  287                             // Convert from Unicode to the system ANSI code page.
  288                             dst_size = WideCharToMultiByte(CP_ACP, 0, wide_char, wide_size, dst, _countof(dst), NULL, NULL);
  289                         }
  290                         else
  291                         {
  292                             src_size = 1; // Seems best to drop only this byte, even if it appeared to be a lead byte.
  293                             dst_size = 0; // Allow the check below to handle it.
  294                         }
  295                     }
  296 #endif
  297                 } // end (*src >= 0x80)
  298             }
  299 
  300             if (dst_size == 1)
  301             {
  302                 // \r\n has already been handled above, even if !(mFlags & EOL_CRLF), so \r at
  303                 // this point can only be \r on its own:
  304                 if (*dst == '\r' && (mFlags & EOL_ORPHAN_CR))
  305                     *dst = '\n';
  306                 if (*dst == '\n')
  307                 {
  308                     if (--aNumLines == 0)
  309                     {
  310                         // Our caller asked for a specific number of lines, which we now have.
  311                         aBuf[target_used++] = '\n';
  312                         mPos = src + src_size;
  313                         if (target_used < aBufLen)
  314                             aBuf[target_used] = '\0';
  315                         return target_used;
  316                     }
  317                 }
  318 
  319                 // If we got to this point, dst contains a single TCHAR:
  320                 aBuf[target_used++] = *dst;
  321             }
  322             else if (dst_size) // Multi-byte/surrogate pair.
  323             {
  324                 if (target_used + dst_size > aBufLen)
  325                 {
  326                     // This multi-byte char/surrogate pair won't fit, so leave it in the file buffer.
  327                     mPos = src;
  328                     aBuf[target_used] = '\0';
  329                     return target_used;
  330                 }
  331                 tmemcpy(aBuf + target_used, dst, dst_size);
  332                 target_used += dst_size;
  333             }
  334             else
  335             {
  336                 aBuf[target_used++] = INVALID_CHAR;
  337             }
  338         } // end for-loop which processes buffered data.
  339         if (src == src_end)
  340         {
  341             // Reset the buffer so that Read() can read a full block.
  342             mLength = 0;
  343             mPos = NULL;
  344         }
  345         else
  346             mPos = src;
  347     } // end for-loop which repopulates the buffer.
  348     if (target_used < aBufLen)
  349         aBuf[target_used] = '\0';
  350     // Otherwise, caller is responsible for reserving one char and null-terminating if needed.
  351     return target_used;
  352 }
  353 
  354 
  355 
  356 DWORD TextStream::Read(LPVOID aBuf, DWORD aBufLen)
  357 {
  358     if (!PrepareToRead() || !aBufLen)
  359         return 0;
  360 
  361     DWORD target_used = 0;
  362     
  363     if (mPos)
  364     {
  365         DWORD data_in_buffer = (DWORD)(mBuffer + mLength - mPos);
  366         if (data_in_buffer >= aBufLen)
  367         {
  368             // The requested amount of data already exists in our buffer, so copy it over.
  369             memcpy(aBuf, mPos, aBufLen);
  370             if (data_in_buffer == aBufLen)
  371             {
  372                 mPos = NULL; // No more data in buffer.
  373                 mLength = 0; //
  374             }
  375             else
  376                 mPos += aBufLen;
  377             return aBufLen;
  378         }
  379         
  380         // Consume all buffered data.  If there is none (i.e. mPos was somehow pointing at the
  381         // end of the buffer), it is crucial that we clear the buffer for the next section.
  382         memcpy(aBuf, mPos, data_in_buffer);
  383         target_used = data_in_buffer;
  384         mLength = 0;
  385         mPos = NULL;
  386     }
  387 
  388     LPBYTE target = (LPBYTE)aBuf + target_used;
  389     DWORD target_remaining = aBufLen - target_used;
  390 
  391     if (target_remaining < TEXT_IO_BLOCK)
  392     {
  393         Read(TEXT_IO_BLOCK);
  394 
  395         if (mLength <= target_remaining)
  396         {
  397             // All of the data read above will fit in the caller's buffer.
  398             memcpy(target, mBuffer, mLength);
  399             target_used += mLength;
  400             mLength = 0;
  401             // Since no data remains in the buffer, mPos can remain set to NULL.
  402             // UPDATE: If (mPos == mBuffer + mLength), it was not set to NULL above.
  403             mPos = NULL;
  404         }
  405         else
  406         {
  407             // Surplus data was read 
  408             memcpy(target, mBuffer, target_remaining);
  409             target_used += target_remaining;
  410             mPos = mBuffer + target_remaining;
  411         }
  412     }
  413     else
  414     {
  415         // The remaining data to be read exceeds the capacity of our buffer, so bypass it.
  416         target_used += _Read(target, target_remaining);
  417     }
  418 
  419     return target_used;
  420 }
  421 
  422 
  423 
  424 DWORD TextStream::Write(LPCTSTR aBuf, DWORD aBufLen)
  425 // Returns the number of bytes aBuf took after performing applicable EOL and
  426 // code page translations.  Since data is buffered, this is generally not the
  427 // amount actually written to file.  Returns 0 on apparent critical failure,
  428 // even if *some* data was written into the buffer and/or to file.
  429 {
  430     if (!PrepareToWrite())
  431         return 0;
  432 
  433     if (aBufLen == 0)
  434     {
  435         aBufLen = (DWORD)_tcslen(aBuf);
  436         if (aBufLen == 0) // Below may rely on this having been checked.
  437             return 0;
  438     }
  439     
  440     DWORD bytes_flushed = 0; // Number of buffered bytes flushed to file; used to calculate our return value.
  441 
  442     LPCTSTR src;
  443     LPCTSTR src_end;
  444     int src_size;
  445     
  446     union {
  447         LPBYTE  dst;
  448         LPSTR   dstA;
  449         LPWSTR  dstW;
  450     };
  451     dst = mBuffer + mLength;
  452     
  453     // Allow enough space in the buffer for any one of the following:
  454     //  a 4-byte UTF-8 sequence
  455     //  a UTF-16 surrogate pair
  456     //  a carriage-return/newline pair
  457     LPBYTE dst_end = mBuffer + TEXT_IO_BLOCK - 4;
  458 
  459     for (src = aBuf, src_end = aBuf + aBufLen; ; )
  460     {
  461         // The following section speeds up writing of ASCII characters by copying as many as
  462         // possible in each iteration of the outer loop, avoiding certain checks that would
  463         // otherwise be made once for each char.  This relies on the fact that ASCII chars
  464         // have the same binary value (but not necessarily width) in every supported encoding.
  465         // EOL logic is also handled here, for performance.  An alternative approach which is
  466         // tidier and performs almost as well is to add (*src != '\n') to each loop's condition
  467         // and handle it after the loop terminates.
  468         if (mCodePage != CP_UTF16)
  469         {
  470             for ( ; src < src_end && !(*src & ~0x7F) && dst < dst_end; ++src)
  471             {
  472                 if (*src == '\n' && (mFlags & EOL_CRLF) && ((src == aBuf) ? mLastWriteChar : src[-1]) != '\r')
  473                     *dstA++ = '\r';
  474                 *dstA++ = (CHAR)*src;
  475             }
  476         }
  477         else
  478         {
  479 #ifdef UNICODE
  480             for ( ; src < src_end && dst < dst_end; ++src)
  481 #else
  482             for ( ; src < src_end && !(*src & ~0x7F) && dst < dst_end; ++src) // No conversion needed for ASCII chars.
  483 #endif
  484             {
  485                 if (*src == '\n' && (mFlags & EOL_CRLF) && ((src == aBuf) ? mLastWriteChar : src[-1]) != '\r')
  486                     *dstW++ = '\r';
  487                 *dstW++ = (WCHAR)*src;
  488             }
  489         }
  490 
  491         if (dst >= dst_end)
  492         {
  493             DWORD len = (DWORD)(dst - mBuffer);
  494             if (_Write(mBuffer, len) < len)
  495             {
  496                 // The following isn't done since there's no way for the caller to know
  497                 // how much of aBuf was successfully translated or written to file, or
  498                 // even how many bytes to expect due to EOL and code page translations:
  499                 //if (written)
  500                 //{
  501                 //  // Since a later call might succeed, remove this data from the buffer
  502                 //  // to prevent it from being written twice.  Note that some or all of
  503                 //  // this data might've been buffered by a previous call.
  504                 //  memmove(mBuffer, mBuffer + written, mLength - written);
  505                 //  mLength -= written;
  506                 //}
  507                 // Instead, dump the contents of the buffer along with the remainder of aBuf,
  508                 // then return 0 to indicate a critical failure.
  509                 mLength = 0;
  510                 return 0;
  511             }
  512             bytes_flushed += len;
  513             dst = mBuffer;
  514             continue; // If *src is ASCII, we want to use the high-performance mode (above).
  515         }
  516 
  517         if (src == src_end)
  518             break;
  519 
  520 #ifdef UNICODE
  521         if (*src >= 0xD800 && *src <= 0xDBFF // i.e. this is a UTF-16 high surrogate.
  522             && src + 1 < src_end // If this is at the end of the string, there is no low surrogate.
  523             && src[1] >= 0xDC00 && src[1] <= 0xDFFF) // This is a complete surrogate pair.
  524 #else
  525         if (IsLeadByteACP((BYTE)*src) && src + 1 < src_end) // src[1] is the second byte of this char.
  526 #endif
  527             src_size = 2;
  528         else
  529             src_size = 1;
  530 
  531 #ifdef UNICODE
  532         ASSERT(mCodePage != CP_UTF16); // An optimization above already handled UTF-16.
  533         dstA += WideCharToMultiByte(mCodePage, 0, src, src_size, dstA, 4, NULL, NULL);
  534         src += src_size;
  535 #else
  536         if (mCodePage == g_ACP)
  537         {
  538             *dst++ = (BYTE)*src++;
  539             if (src_size == 2)
  540                 *dst++ = (BYTE)*src++;
  541         }
  542         else
  543         {
  544             WCHAR wc;
  545             if (MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, src, src_size, &wc, 1))
  546             {
  547                 if (mCodePage == CP_UTF16)
  548                     *dstW++ = wc;
  549                 else
  550                     dstA += WideCharToMultiByte(mCodePage, 0, &wc, 1, (LPSTR)dst, 4, NULL, NULL);
  551             }
  552             src += src_size;
  553         }
  554 #endif
  555     }
  556 
  557     mLastWriteChar = src_end[-1]; // So if this is \r and the next char is \n, don't make it \r\r\n.
  558 
  559     DWORD initial_length = mLength;
  560     mLength = (DWORD)(dst - mBuffer);
  561     return bytes_flushed + mLength - initial_length; // Doing it this way should perform better and result in smaller code than counting each byte put into the buffer.
  562 }
  563 
  564 
  565 
  566 DWORD TextStream::Write(LPCVOID aBuf, DWORD aBufLen)
  567 {
  568     if (!PrepareToWrite())
  569         return 0;
  570 
  571     if (aBufLen < TEXT_IO_BLOCK - mLength) // There would be room for at least 1 byte after appending data.
  572     {
  573         // Buffer the data.
  574         memcpy(mBuffer + mLength, aBuf, aBufLen);
  575         mLength += aBufLen;
  576         return aBufLen;
  577     }
  578     else
  579     {
  580         // data is bigger than the remaining space in the buffer.  If (len < TEXT_IO_BLOCK*2 - mLength), we
  581         // could copy the first part of data into the buffer, flush it, then write the remainder into the
  582         // buffer to await more text to be buffered.  However, the need for a memcpy combined with the added
  583         // code size and complexity mean it probably isn't worth doing.
  584         if (mLength)
  585         {
  586             _Write(mBuffer, mLength);
  587             mLength = 0;
  588         }
  589         return _Write(aBuf, aBufLen);
  590     }
  591 }
  592 
  593 
  594 
  595 //
  596 // TextFile
  597 //
  598 bool TextFile::_Open(LPCTSTR aFileSpec, DWORD &aFlags)
  599 {
  600     _Close();
  601     DWORD dwDesiredAccess, dwShareMode, dwCreationDisposition;
  602     switch (aFlags & ACCESS_MODE_MASK) {
  603         case READ:
  604             dwDesiredAccess = GENERIC_READ;
  605             dwCreationDisposition = OPEN_EXISTING;
  606             break;
  607         case WRITE:
  608             dwDesiredAccess = GENERIC_WRITE;
  609             dwCreationDisposition = CREATE_ALWAYS;
  610             break;
  611         case APPEND:
  612         case UPDATE:
  613             dwDesiredAccess = GENERIC_WRITE | GENERIC_READ;
  614             dwCreationDisposition = OPEN_ALWAYS;
  615             break;
  616         case USEHANDLE:
  617             if (!GetFileType((HANDLE)aFileSpec))
  618                 return false;
  619             mFile = (HANDLE)aFileSpec;
  620             return true;
  621     }
  622     dwShareMode = ((aFlags >> 8) & (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE));
  623 
  624     if (*aFileSpec == '*')
  625     {
  626         // v1.1.17: Allow FileOpen("*", "r|w") to open stdin/stdout/stderr ("**" for stderr).
  627         // Can also be used to read script text from stdin, by passing "*" as the filename.
  628         DWORD nStdHandle = 0;
  629         switch (aFlags & ACCESS_MODE_MASK)
  630         {
  631         case APPEND:
  632             // Allow FileAppend to write to stdout/stderr via TextStream.
  633             aFlags = (aFlags & ~ACCESS_MODE_MASK) | READ;
  634         case WRITE:
  635             if (!aFileSpec[1])
  636                 nStdHandle = STD_OUTPUT_HANDLE;
  637             else if (aFileSpec[1] == '*' && !aFileSpec[2])
  638                 nStdHandle = STD_ERROR_HANDLE;
  639             break;
  640         case READ:
  641             if (!aFileSpec[1])
  642                 nStdHandle = STD_INPUT_HANDLE;
  643             break;
  644         }
  645         if (nStdHandle) // It was * or ** and not something invalid like *Somefile.
  646         {
  647             HANDLE hstd = GetStdHandle(nStdHandle);
  648             if (hstd == NULL)// || !DuplicateHandle(GetCurrentProcess(), hstd, GetCurrentProcess(), &hstd, 0, FALSE, DUPLICATE_SAME_ACCESS))
  649                 return false;
  650             aFlags = (aFlags & ~ACCESS_MODE_MASK) | USEHANDLE; // Avoid calling CloseHandle(), since we don't own it.
  651             mFile = hstd; // Only now that we know it's not NULL.
  652             return true;
  653         }
  654         // For any case not handled above, such as WRITE|READ combined or *** or *Somefile,
  655         // it should be detected as an error below by CreateFile() failing (or if not, it's
  656         // somehow valid and should not be treated as an error).
  657     }
  658     
  659     // FILE_FLAG_SEQUENTIAL_SCAN is set, as sequential accesses are quite common for text files handling.
  660     mFile = CreateFile(aFileSpec, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition,
  661         (aFlags & (EOL_CRLF | EOL_ORPHAN_CR)) ? FILE_FLAG_SEQUENTIAL_SCAN : 0, NULL);
  662 
  663     return mFile != INVALID_HANDLE_VALUE;
  664 }
  665 
  666 void TextFile::_Close()
  667 {
  668     if (mFile != INVALID_HANDLE_VALUE) {
  669         if ((mFlags & ACCESS_MODE_MASK) != USEHANDLE)
  670             CloseHandle(mFile);
  671         mFile = INVALID_HANDLE_VALUE;
  672     }
  673 }
  674 
  675 DWORD TextFile::_Read(LPVOID aBuffer, DWORD aBufSize)
  676 {
  677     DWORD dwRead = 0;
  678     ReadFile(mFile, aBuffer, aBufSize, &dwRead, NULL);
  679     return dwRead;
  680 }
  681 
  682 DWORD TextFile::_Write(LPCVOID aBuffer, DWORD aBufSize)
  683 {
  684     DWORD dwWritten = 0;
  685     WriteFile(mFile, aBuffer, aBufSize, &dwWritten, NULL);
  686     return dwWritten;
  687 }
  688 
  689 bool TextFile::_Seek(__int64 aDistance, int aOrigin)
  690 {
  691     return !!SetFilePointerEx(mFile, *((PLARGE_INTEGER) &aDistance), NULL, aOrigin);
  692 }
  693 
  694 __int64 TextFile::_Tell() const
  695 {
  696     LARGE_INTEGER in = {0}, out;
  697     return SetFilePointerEx(mFile, in, &out, FILE_CURRENT) ? out.QuadPart : -1;
  698 }
  699 
  700 __int64 TextFile::_Length() const
  701 {
  702     LARGE_INTEGER size;
  703     GetFileSizeEx(mFile, &size);
  704     return size.QuadPart;
  705 }
  706 
  707 
  708 // FileObject: exports TextFile interfaces to the scripts.
  709 class FileObject : public ObjectBase // fincs: No longer allowing the script to manipulate File objects
  710 {
  711     FileObject() {}
  712     ~FileObject() {}
  713 
  714     enum MemberID {
  715         INVALID = 0,
  716         // methods
  717         Read,
  718         Write,
  719         ReadLine,
  720         WriteLine,
  721         NumReadWrite,
  722         RawReadWrite,
  723         LastMethodPlusOne,
  724         // properties
  725         Position,
  726         Length,
  727         AtEOF,
  728         Handle,
  729         Encoding,
  730         Close
  731     };
  732 
  733     ResultType STDMETHODCALLTYPE Invoke(ExprTokenType &aResultToken, ExprTokenType &aThisToken, int aFlags, ExprTokenType *aParam[], int aParamCount)
  734     // Reference: MetaObject::Invoke
  735     {
  736         if (!aParamCount) // file[]
  737             return INVOKE_NOT_HANDLED;
  738         
  739         --aParamCount; // Exclude name from param count.
  740         LPTSTR name = TokenToString(*aParam[0]); // Name of method or property.
  741         MemberID member = INVALID;
  742 
  743         // Read' and Write' must be handled differently to support ReadUInt(), WriteShort(), etc.
  744         if (!_tcsnicmp(name, _T("Read"), 4))
  745         {
  746             if (!name[4])
  747                 member = Read;
  748             else if (!_tcsicmp(name + 4, _T("Line")))
  749                 member = ReadLine;
  750             else
  751                 member = NumReadWrite;
  752         }
  753         else if (!_tcsnicmp(name, _T("Write"), 5))
  754         {
  755             if (!name[5])
  756                 member = Write;
  757             else if (!_tcsicmp(name + 5, _T("Line")))
  758                 member = WriteLine;
  759             else
  760                 member = NumReadWrite;
  761         }
  762     #define if_member(s,e)  else if (!_tcsicmp(name, _T(s))) member = e;
  763         if_member("RawRead", RawReadWrite)
  764         if_member("RawWrite", RawReadWrite)
  765         if_member("Pos", Position)
  766         if_member("Length", Length)
  767         if_member("AtEOF", AtEOF)
  768         if_member("__Handle", Handle) // Prefix with underscores because it is designed for advanced users.
  769         if_member("Encoding", Encoding)
  770         if_member("Close", Close)
  771         // Supported for enhanced clarity:
  772         if_member("Position", Position)
  773         // Legacy names:
  774         if_member("Seek", Position)
  775         if_member("Tell", Position)
  776     #undef if_member
  777         if (member == INVALID)
  778             return INVOKE_NOT_HANDLED;
  779 
  780         // Syntax validation:
  781         if (!IS_INVOKE_CALL)
  782         {
  783             if (member < LastMethodPlusOne)
  784                 // Member requires parentheses().
  785                 return OK;
  786             if (aParamCount != (IS_INVOKE_SET ? 1 : 0))
  787                 // Get: disallow File.Length[newLength] and File.Seek[dist,origin].
  788                 // Set: disallow File[]:=PropertyName and File["Pos",dist]:=origin.
  789                 return OK;
  790         }
  791 
  792         aResultToken.symbol = SYM_INTEGER; // Set default return type -- the most common cases return integer.
  793 
  794         switch (member)
  795         {
  796         case NumReadWrite:
  797             {
  798                 bool reading = (*name == 'R' || *name == 'r');
  799                 LPCTSTR type = name + (reading ? 4 : 5);
  800 
  801                 // Based on BIF_NumGet:
  802 
  803                 BOOL is_signed, is_float = FALSE;
  804                 DWORD size = 0;
  805 
  806                 if (ctoupper(*type) == 'U') // Unsigned.
  807                 {
  808                     ++type; // Remove the first character from further consideration.
  809                     is_signed = FALSE;
  810                 }
  811                 else
  812                     is_signed = TRUE;
  813 
  814                 switch(ctoupper(*type)) // Override "size" and aResultToken.symbol if type warrants it. Note that the above has omitted the leading "U", if present, leaving type as "Int" vs. "Uint", etc.
  815                 {
  816                 case 'I':
  817                     if (_tcschr(type, '6')) // Int64. It's checked this way for performance, and to avoid access violation if string is bogus and too short such as "i64".
  818                         size = 8;
  819                     else
  820                         size = 4;
  821                     break;
  822                 case 'S': size = 2; break; // Short.
  823                 case 'C': size = 1; break; // Char.
  824                 case 'P': size = sizeof(INT_PTR); break;
  825 
  826                 case 'D': size = 8; is_float = true; break; // Double.
  827                 case 'F': size = 4; is_float = true; break; // Float.
  828                 }
  829                 if (!size)
  830                     break; // Return "" or throw.
  831 
  832                 union {
  833                         __int64 i8;
  834                         int i4;
  835                         short i2;
  836                         char i1;
  837                         double d;
  838                         float f;
  839                     } buf;
  840 
  841                 if (reading)
  842                 {
  843                     buf.i8 = 0;
  844                     if ( !mFile.Read(&buf, size) )
  845                         break; // Return "" or throw.
  846 
  847                     if (is_float)
  848                     {
  849                         aResultToken.value_double = (size == 4) ? buf.f : buf.d;
  850                         aResultToken.symbol = SYM_FLOAT;
  851                     }
  852                     else
  853                     {
  854                         if (is_signed)
  855                         {
  856                             // sign-extend to 64-bit
  857                             switch (size)
  858                             {
  859                             case 4: buf.i8 = buf.i4; break;
  860                             case 2: buf.i8 = buf.i2; break;
  861                             case 1: buf.i8 = buf.i1; break;
  862                             //case 8: not needed.
  863                             }
  864                         }
  865                         //else it's unsigned. No need to zero-extend thanks to init done earlier.
  866                         aResultToken.value_int64 = buf.i8;
  867                         //aResultToken.symbol = SYM_INTEGER; // This is the default.
  868                     }
  869                 }
  870                 else
  871                 {
  872                     if (aParamCount != 1)
  873                         break; // Return "" or throw.
  874 
  875                     ExprTokenType &token_to_write = *aParam[1];
  876                     
  877                     if (is_float)
  878                     {
  879                         buf.d = TokenToDouble(token_to_write);
  880                         if (size == 4)
  881                             buf.f = (float)buf.d;
  882                     }
  883                     else
  884                     {
  885                         if (size == 8 && !is_signed && !IS_NUMERIC(token_to_write.symbol))
  886                             buf.i8 = (__int64)ATOU64(TokenToString(token_to_write)); // For comments, search for ATOU64 in BIF_DllCall().
  887                         else
  888                             buf.i8 = TokenToInt64(token_to_write);
  889                     }
  890                     
  891                     DWORD bytes_written = mFile.Write(&buf, size);
  892                     if (!bytes_written && g->InTryBlock())
  893                         break; // Throw an exception.
  894                     // Otherwise, we should return bytes_written even if it is 0:
  895                     aResultToken.value_int64 = bytes_written;
  896                 }
  897                 return OK;
  898             }
  899             break;
  900 
  901         case Read:
  902             if (aParamCount <= 1)
  903             {
  904                 DWORD length;
  905                 if (aParamCount)
  906                     length = (DWORD)TokenToInt64(*aParam[1]);
  907                 else
  908                     length = (DWORD)(mFile.Length() - mFile.Tell()); // We don't know the actual number of characters these bytes will translate to, but this should be sufficient.
  909                 if (length == -1 || !TokenSetResult(aResultToken, NULL, length)) // Relies on short-circuit order. TokenSetResult requires non-NULL aResult if aResultLength == -1.
  910                     break; // Return "" or throw.
  911                 length = mFile.Read(aResultToken.marker, length);
  912                 aResultToken.symbol = SYM_STRING;
  913                 aResultToken.marker[length] = '\0';
  914                 aResultToken.marker_length = length; // Update marker_length to the actual number of characters read. Only strictly necessary in some cases; see TokenSetResult.
  915                 return OK;
  916             }
  917             break;
  918         
  919         case ReadLine:
  920             if (aParamCount == 0)
  921             {   // See above for comments.
  922                 if (!TokenSetResult(aResultToken, NULL, READ_FILE_LINE_SIZE))
  923                     break; // Return "" or throw.
  924                 DWORD length = mFile.ReadLine(aResultToken.marker, READ_FILE_LINE_SIZE - 1);
  925                 aResultToken.symbol = SYM_STRING;
  926                 aResultToken.marker[length] = '\0';
  927                 aResultToken.buf = (LPTSTR)(size_t) length;
  928                 return OK;
  929             }
  930             break;
  931 
  932         case Write:
  933         case WriteLine:
  934             if (aParamCount <= 1)
  935             {
  936                 DWORD bytes_written = 0, chars_to_write = 0;
  937                 if (aParamCount)
  938                 {
  939                     LPTSTR param1 = TokenToString(*aParam[1], aResultToken.buf);
  940                     chars_to_write = (DWORD)EXPR_TOKEN_LENGTH(aParam[1], param1);
  941                     bytes_written = mFile.Write(param1, chars_to_write);
  942                 }
  943                 if (member == WriteLine && (bytes_written || !chars_to_write)) // i.e. don't attempt it if above failed.
  944                 {
  945                     chars_to_write += 1;
  946                     bytes_written += mFile.Write(_T("\n"), 1);
  947                 }
  948                 // If no data was written and some should have been, consider it a failure:
  949                 if (!bytes_written && chars_to_write && g->InTryBlock())
  950                     break; // Throw an exception.
  951                 // Otherwise, some data was written (partial writes are considered successful),
  952                 // no data was requested to be written, or no TRY block is active, so we need to
  953                 // return the value of bytes_written even if it is 0:
  954                 aResultToken.value_int64 = bytes_written;
  955                 return OK;
  956             }
  957             break;
  958 
  959         case RawReadWrite:
  960             if (aParamCount == 2)
  961             {
  962                 bool reading = (name[3] == 'R' || name[3] == 'r');
  963 
  964                 LPVOID target;
  965                 ExprTokenType &target_token = *aParam[1];
  966                 DWORD size = (DWORD)TokenToInt64(*aParam[2]);
  967 
  968                 if (target_token.symbol == SYM_VAR) // SYM_VAR's Type() is always VAR_NORMAL (except lvalues in expressions).
  969                 {
  970                     // Check if the user requested a size larger than the variable.
  971                     if ( size > target_token.var->ByteCapacity()
  972                         // Too small: expand the target variable if reading; abort otherwise.
  973                         && (!reading || !target_token.var->SetCapacity(size, false, false)) ) // Relies on short-circuit order.
  974                     {
  975                         if (g->InTryBlock())
  976                             break; // Throw an exception.
  977                         aResultToken.value_int64 = 0;
  978                         return OK;
  979                     }
  980                     target = target_token.var->Contents();
  981                 }
  982                 else
  983                     target = (LPVOID)TokenToInt64(target_token);
  984 
  985                 DWORD result;
  986                 if (target < (LPVOID)65536) // Basic sanity check to catch incoming raw addresses that are zero or blank.
  987                     result = 0;
  988                 else if (reading)
  989                     result = mFile.Read(target, size);
  990                 else
  991                     result = mFile.Write(target, size);
  992                 if (!result && size && g->InTryBlock())
  993                     break; // Throw an exception.
  994                 // Otherwise, it was a complete or partial success, or no TRY block is active.
  995                 aResultToken.value_int64 = result;
  996                 return OK;
  997             }
  998             break;
  999 
 1000         case Position:
 1001             if (aParamCount == 0)
 1002             {
 1003                 aResultToken.value_int64 = mFile.Tell();
 1004                 return OK;
 1005             }
 1006             else if (aParamCount <= 2)
 1007             {
 1008                 __int64 distance = TokenToInt64(*aParam[1]);
 1009                 int origin;
 1010                 if (aParamCount == 2)
 1011                     origin = (int)TokenToInt64(*aParam[2]);
 1012                 else // Defaulting to SEEK_END when distance is negative seems more useful than allowing it to be interpreted as an unsigned value (> 9.e18 bytes).
 1013                     origin = (distance < 0) ? SEEK_END : SEEK_SET;
 1014 
 1015                 if (!mFile.Seek(distance, origin))
 1016                 {
 1017                     if (g->InTryBlock())
 1018                         break; // Throw an exception.
 1019                     aResultToken.value_int64 = 0;
 1020                 }
 1021                 else
 1022                     aResultToken.value_int64 = 1;
 1023                 return OK;
 1024             }
 1025             break;
 1026 
 1027         case Length:
 1028             if (aParamCount == 0)
 1029             {
 1030                 aResultToken.value_int64 = mFile.Length();
 1031                 return OK;
 1032             }
 1033             else if (aParamCount == 1)
 1034             {
 1035                 if (-1 != (aResultToken.value_int64 = mFile.Length(TokenToInt64(*aParam[1]))))
 1036                     return OK;
 1037                 else // Empty string seems like a more suitable failure indicator than -1.
 1038                     aResultToken.marker = _T("");
 1039                     // Let below set symbol back to SYM_STRING and throw an exception if appropriate.
 1040             }
 1041             break;
 1042 
 1043         case AtEOF:
 1044             if (aParamCount == 0)
 1045                 aResultToken.value_int64 = mFile.AtEOF();
 1046             return OK;
 1047         
 1048         case Handle:
 1049             if (aParamCount == 0)
 1050                 aResultToken.value_int64 = (UINT_PTR) mFile.Handle();
 1051             return OK;
 1052 
 1053         case Encoding:
 1054         {
 1055             // Encoding: UTF-8, UTF-16 or CPnnn.  The -RAW suffix (CP_AHKNOBOM) is not supported; it is normally
 1056             // stripped out when the file is opened, so passing it to SetCodePage() would break encoding/decoding
 1057             // of non-ASCII characters (and did in v1.1.15.03 and earlier).  Although it could be detected/added
 1058             // via TextStream::mFlags, this isn't done because:
 1059             //  - It would only tell us whether the script passed "-RAW", not whether the file really has a BOM.
 1060             //  - It's questionable which behaviour is more more useful, but excluding "-RAW" is definitely simpler.
 1061             //  - Existing scripts may rely on File.Encoding not returning "-RAW".
 1062             UINT codepage;
 1063             if (aParamCount > 0)
 1064             {
 1065                 if (TokenIsPureNumeric(*aParam[1]))
 1066                     codepage = (UINT)TokenToInt64(*aParam[1]);
 1067                 else
 1068                     codepage = Line::ConvertFileEncoding(TokenToString(*aParam[1]));
 1069                 if (codepage != -1)
 1070                     mFile.SetCodePage(codepage & ~CP_AHKNOBOM); // Ignore "-RAW" by removing the CP_AHKNOBOM flag; see comments above.
 1071                 // Now fall through to below and return the actual codepage.
 1072             }
 1073             LPTSTR name;
 1074             codepage = mFile.GetCodePage();
 1075             // There's no need to check for the CP_AHKNOBOM flag here because it's stripped out when the file is opened.
 1076             switch (codepage)
 1077             {
 1078             // GetCodePage() returns the value of GetACP() in place of CP_ACP, so this case is not needed:
 1079             //case CP_ACP:  name = _T("");  break;
 1080             case CP_UTF8:   name = _T("UTF-8");  break;
 1081             case CP_UTF16:  name = _T("UTF-16"); break;
 1082             default:
 1083                 // Although we could check codepage == GetACP() and return blank in that case, there's no way
 1084                 // to know whether something like "CP0" or the actual codepage was passed to FileOpen, so just
 1085                 // return "CPn" when none of the cases above apply:
 1086                 name = aResultToken.buf;
 1087                 name[0] = _T('C');
 1088                 name[1] = _T('P');
 1089                 _itot(codepage, name + 2, 10);
 1090             }
 1091             aResultToken.symbol = SYM_STRING;
 1092             aResultToken.marker = name;
 1093             return OK;
 1094         }
 1095 
 1096         case Close:
 1097             if (aParamCount == 0)
 1098                 mFile.Close();
 1099             return OK;
 1100         }
 1101         
 1102         // Since above didn't return, an error must've occurred.
 1103         aResultToken.symbol = SYM_STRING;
 1104         // marker should already be set to "".
 1105         if (g->InTryBlock())
 1106             // For simplicity, don't attempt to identify what kind of error occurred:
 1107             Script::ThrowRuntimeException(ERRORLEVEL_ERROR, _T("FileObject"));
 1108         return OK;
 1109     }
 1110 
 1111     IObject_Type_Impl("File")
 1112 
 1113     TextFile mFile;
 1114     
 1115 public:
 1116     static inline FileObject *Open(LPCTSTR aFileSpec, DWORD aFlags, UINT aCodePage)
 1117     {
 1118         FileObject *fileObj = new FileObject();
 1119         if (fileObj && fileObj->mFile.Open(aFileSpec, aFlags, aCodePage))
 1120             return fileObj;
 1121         fileObj->Release();
 1122         return NULL;
 1123     }
 1124 };
 1125 
 1126 BIF_DECL(BIF_FileOpen)
 1127 {
 1128     DWORD aFlags;
 1129     UINT aEncoding;
 1130 
 1131     if (TokenIsPureNumeric(*aParam[1]))
 1132     {
 1133         aFlags = (DWORD) TokenToInt64(*aParam[1]);
 1134     }
 1135     else
 1136     {
 1137         LPCTSTR sflag = TokenToString(*aParam[1], aResultToken.buf);
 1138 
 1139         sflag = omit_leading_whitespace(sflag); // For consistency with the loop below.
 1140 
 1141         // Access mode must come first:
 1142         switch (tolower(*sflag))
 1143         {
 1144         case 'r':
 1145             if (tolower(sflag[1]) == 'w')
 1146             {
 1147                 aFlags = TextStream::UPDATE;
 1148                 ++sflag;
 1149             }
 1150             else
 1151                 aFlags = TextStream::READ;
 1152             break;
 1153         case 'w': aFlags = TextStream::WRITE; break;
 1154         case 'a': aFlags = TextStream::APPEND; break;
 1155         case 'h': aFlags = TextStream::USEHANDLE; break;
 1156         default:
 1157             // Invalid flag.
 1158             goto invalid_param;
 1159         }
 1160         
 1161         // Default to not locking file, for consistency with fopen/standard AutoHotkey and because it seems best for flexibility.
 1162         aFlags |= TextStream::SHARE_ALL;
 1163 
 1164         for (++sflag; *sflag; ++sflag)
 1165         {
 1166             switch (ctolower(*sflag))
 1167             {
 1168             case '\n': aFlags |= TextStream::EOL_CRLF; break;
 1169             case '\r': aFlags |= TextStream::EOL_ORPHAN_CR; break;
 1170             case ' ':
 1171             case '\t':
 1172                 // Allow spaces and tabs for readability.
 1173                 break;
 1174             case '-':
 1175                 for (++sflag; ; ++sflag)
 1176                 {
 1177                     switch (ctolower(*sflag))
 1178                     {
 1179                     case 'r': aFlags &= ~TextStream::SHARE_READ; continue;
 1180                     case 'w': aFlags &= ~TextStream::SHARE_WRITE; continue;
 1181                     case 'd': aFlags &= ~TextStream::SHARE_DELETE; continue;
 1182                     // Whitespace not allowed here.  Outer loop allows "-r -w" but not "-r w".
 1183                     }
 1184                     if (sflag[-1] == '-')
 1185                         // Let "-" on its own be equivalent to "-rwd".
 1186                         aFlags &= ~TextStream::SHARE_ALL;
 1187                     break;
 1188                 }
 1189                 --sflag; // Point sflag at the last char of this option.  Outer loop will do ++sflag.
 1190                 break;
 1191             default:
 1192                 // Invalid flag.
 1193                 goto invalid_param;
 1194             }
 1195         }
 1196     }
 1197 
 1198     if (aParamCount > 2)
 1199     {
 1200         if (!TokenIsPureNumeric(*aParam[2]))
 1201         {
 1202             aEncoding = Line::ConvertFileEncoding(TokenToString(*aParam[2]));
 1203             if (aEncoding == -1)
 1204             {   // Invalid param.
 1205                 goto invalid_param;
 1206             }
 1207         }
 1208         else aEncoding = (UINT) TokenToInt64(*aParam[2]);
 1209     }
 1210     else aEncoding = g->Encoding;
 1211     
 1212     ASSERT( (~CP_AHKNOBOM) == CP_AHKCP );
 1213     // aEncoding may include CP_AHKNOBOM, in which case below will not add BOM_UTFxx flag.
 1214     if (aEncoding == CP_UTF8)
 1215         aFlags |= TextStream::BOM_UTF8;
 1216     else if (aEncoding == CP_UTF16)
 1217         aFlags |= TextStream::BOM_UTF16;
 1218 
 1219     LPTSTR aFileName;
 1220     if ((aFlags & TextStream::ACCESS_MODE_MASK) == TextStream::USEHANDLE)
 1221         aFileName = (LPTSTR)(HANDLE)TokenToInt64(*aParam[0]);
 1222     else
 1223         aFileName = TokenToString(*aParam[0], aResultToken.buf);
 1224 
 1225     if (aResultToken.object = FileObject::Open(aFileName, aFlags, aEncoding & CP_AHKCP))
 1226         aResultToken.symbol = SYM_OBJECT;
 1227 
 1228     g->LastError = GetLastError(); // Even on success, since it might provide something useful.
 1229     
 1230     if (!aResultToken.object)
 1231     {
 1232         aResultToken.value_int64 = 0; // and symbol is already SYM_INTEGER.
 1233         if (g->InTryBlock())
 1234             Script::ThrowRuntimeException(_T("Failed to open file."), _T("FileOpen"));
 1235     }
 1236 
 1237     return;
 1238 
 1239 invalid_param:
 1240     aResultToken.value_int64 = 0;
 1241     g->LastError = ERROR_INVALID_PARAMETER; // For consistency.
 1242     if (g->InTryBlock())
 1243         Script::ThrowRuntimeException(ERR_PARAM2_INVALID, _T("FileOpen"));
 1244 }
 1245 
 1246 
 1247 //
 1248 // TextMem
 1249 //
 1250 bool TextMem::_Open(LPCTSTR aFileSpec, DWORD &aFlags)
 1251 {
 1252     ASSERT( (aFlags & ACCESS_MODE_MASK) == TextStream::READ ); // Only read mode is supported.
 1253 
 1254     if (mData.mOwned && mData.mBuffer)
 1255         free(mData.mBuffer);
 1256     mData = *(Buffer *)aFileSpec; // Struct copy.
 1257     mDataPos = (LPBYTE)mData.mBuffer;
 1258     mPos = NULL; // Discard temp buffer contents, if any.
 1259     mLength = 0;
 1260     return true;
 1261 }
 1262 
 1263 void TextMem::_Close()
 1264 {
 1265     if (mData.mBuffer) {
 1266         if (mData.mOwned)
 1267             free(mData.mBuffer);
 1268         mData.mBuffer = NULL;
 1269     }
 1270 }
 1271 
 1272 DWORD TextMem::_Read(LPVOID aBuffer, DWORD aBufSize)
 1273 {
 1274     DWORD remainder = (DWORD)((LPBYTE)mData.mBuffer + mData.mLength - mDataPos);
 1275     if (aBufSize > remainder)
 1276         aBufSize = remainder;
 1277     memmove(aBuffer, mDataPos, aBufSize);
 1278     mDataPos += aBufSize;
 1279     return aBufSize;
 1280 }
 1281 
 1282 DWORD TextMem::_Write(LPCVOID aBuffer, DWORD aBufSize)
 1283 {
 1284     return 0;
 1285 }
 1286 
 1287 bool TextMem::_Seek(__int64 aDistance, int aOrigin)
 1288 {
 1289     return false;
 1290 }
 1291 
 1292 __int64 TextMem::_Tell() const
 1293 {
 1294     return (__int64) (mDataPos - (LPBYTE)mData.mBuffer);
 1295 }
 1296 
 1297 __int64 TextMem::_Length() const
 1298 {
 1299     return mData.mLength;
 1300 }