• The Old New Thing

    If the Euro 2004 tournament were a video game


    If the Euro 2004 tournament were a video game, it might have looked like this. View recreations of the tournament highlights, and since the graphics are computer-generated, you can select among multiple camera angles, embody a specific player, or see what it's like to be the ball itself!

    (Via MetaFilter.)

    [Raymond is currently on vacation; this message was pre-recorded.]

  • The Old New Thing

    Why doesn't Setup asks you if you want to keep newer versions of OS files?


    Windows 95 Setup would notice that a file it was installing was older than the file already on the machine and would ask you whether you wanted to keep the existing (newer) file or to overwrite it with the older version.

    Asking the user this question at all turned out to have been a bad idea. It's one of those dialogs that ask the user a question they have no idea how to answer.

    Say you're installing Windows 95 and you get the file version conflict dialog box. "The file Windows is attempting to install is older than the one already on the system. Do you want to keep the newer file?" What do you do?

    Well, if you're like most people, you say, "Um, I guess I'll keep the newer one," so you click Yes.

    And then a few seconds later, you get the same prompt for some other file. And you say Yes again.

    And then a few seconds later, you get the same prompt for yet another file. Now you're getting nervous. Why is the system asking you all these questions? Is it second-guessing your previous answers? Often when this happens, it's because you're doing something bad and the computer is giving you one more chance to change your mind before something horrible happens. Like in the movies when you have to type Yes five times before it will launch the nuclear weapons.

    Maybe this is one of those times.

    Now you start saying No. Besides, it's always safer to say No, isn't it?

    After a few more dialogs (answering No this time), Setup finally completes. The system reboots, and... it bluescreens.


    Because those five files were part of a matched set of files that together form your video driver. By saying Yes to some of them and No to others, you ended up with a mishmash of files that don't work together.

    We learned our lesson. Setup doesn't ask this question any more. It always overwrites the files with the ones that come with the operating system. Sure, you may lose functionality, but at least you will be able to boot. Afterwards, you can go to Windows Update and update that driver to the latest version.

    Note, however, that this rule does not apply to hotfixes and Service Packs.

    [Raymond is currently on vacation; this message was pre-recorded.]

  • The Old New Thing

    Let WMI do the heavy lifting of determining system information


    Windows Management Instrumentation is a scriptable interface to configuration information. This saves you the trouble of having to figure it out yourself.

    For example, here's a little program that enumerates all the CPUs in your system and prints some basic information about them.

    var locator = WScript.CreateObject("WbemScripting.SWbemLocator");
    var services = locator.ConnectServer();
    var cpus = new Enumerator(services.ExecQuery("SELECT * FROM Win32_Processor"));
    while (!cpus.atEnd()) {
      var cpu = cpus.item();
      WScript.StdOut.WriteLine("cpu.ProcessorType=" + cpu.ProcessorType);
      WScript.StdOut.WriteLine("cpu.CurrentClockSpeed=" + cpu.CurrentClockSpeed);
      WScript.StdOut.WriteLine("cpu.MaxClockSpeed=" + cpu.MaxClockSpeed);
      WScript.StdOut.WriteLine("cpu.Manufacturer=" + cpu.Manufacturer);

    Save this program as cpus.js and run it via cscript cpus.js.

    There's a whole lot of other information kept inside WMI. You can get lost amidst all the classes that exist. The Scripting Guys have their own tool called WMI Scriptomatic which lets you cruise around the WMI namespace. (The Scripting Guys also wrote Tweakomatic which comes with hilarious documentation.)

    Added 11am: It appears that people have misunderstood the point of this entry. The point here is not to show how to print the results to the screen. (I just did that to prove it actually worked.) The point is that you can let WMI do the hard work of actually digging up the information instead of having to hunt it down yourself. Want BIOS information? Try Win32_BIOS. Change the query to "SELECT * FROM Win32_BIOS" and you can get the manufacturer from the Manufacturer property. Plenty more examples in MSDN.

  • The Old New Thing

    How does Explorer detect whether your program supports long file names?


    When you register your program with a file association, the shell needs to decide whether your program supports long file names so it can decide whether to pass you the long name (which may contains spaces! so make sure you put quotation marks around the "%1" in your registration) or the short name.

    The rule is simple: The shell looks at your program's EXE header to see what kind of program it is.

    • If it is a 16-bit program, then the shell assumes that it supports long file names if it is marked as Windows 95-compatible. Otherwise, the shell assumes that it does not support long file anmes.
    • If it is a 32-bit program (or 64-bit program for 64-bit systems), then the shell assumes that it supports long file names.
    • If it can't find your program, then the shell plays it safe and assumes that the program doesn't support long file names.

    Note that third case. If you mess up your program registration, then the shell will be unable to determine whether your program supports long file names and assumes not. Then when your program displays the file name in, say, the title bar, you end up displaying some icky short file name alias instead of the proper long file name that the user expects to see.

    The most common way people mess up their program registration is by forgetting to quote spaces in the path to the program itself! For example, an erroneous registration might go something like this:

                        (default) = C:\Program Files\LitWare Deluxe\litware.exe "%1"

    Observe that the spaces in the path "C:\Program Files\Litware Deluxe\litware.exe" are not quoted in the program registration. Consequently, the shell mistakenly believes that the program name is "C:\Program", which it cannot find. The shell therefore plays it safe and assumes no LFN support.

    Compatibility note: As part of other security work, the code in the shell that parses these command lines was augmented to chase down the "intended" path of the program. This presented the opportunity to fix that third case, so that the shell could find the program after all and see that it supported long file names, thereby saving the user the ignominy of seeing their wonderful file name turn into a mush of tildes.

    And after we made the change, we had to take it out.

    Because there were programs that not only registered themselves incorrectly, but were relying on the shell not being smart enough to find their real location, resulting in the program receiving the short name on the command line. Turns out these programs wanted the short name, and doing this fake-out was their way of accomplishing it.

    (And to those of you who are already shouting, "Go ahead and break them," that's all fine and good as long as the thing that's incompatible isn't something you use. But if it's your program, or a program your company relies on, I expect you're going to change your tune.)

  • The Old New Thing

    The compatibility constraints of even your internal bookkeeping


    The Listview control when placed in report mode has a child header control which it uses to display column header titles. This header control is the property of the listview, but the listview is kind enough to let you retrieve the handle to that header control.

    And some programs abuse that kindness.

    It so happens that the original listview control did not use the lParam of the header control item for anything. So some programs said, "Well, if you're not using it, then I will!" and stashed their own private data into it.

    Then a later version of the listview decided, "Gosh, there's some data I need to keep track of for each header item. Fortunately, since this is my header control, I can stash my data in the lParam of the header item."

    And then the application compatibility team takes those two ingredients (the program that stuffs data into the header control and the listview that does the same) to their laboratory, mixes them, and an explosion occurs.

    After some forensic analysis, the listview development team figures out what happened and curses that they have to work around yet another program that grovels into internal data structures. The auxiliary data is now stored in some other less convenient place so those programs can continue to run without crashing.

    The moral of the story: Even if you change something that nobody should be relying on, there's a decent chance that somebody is relying on it.

    (I'm sure there will be the usual chorus of people who will say, "You should've just broken them." What if I told you that one of the programs that does this is a widly-used system administration tool? Eh, that probably wouldn't change your mind.)

  • The Old New Thing

    Implementing higher-order clicks


    Another question people ask is "How do I do triple-click or higher?" Once you see the algorithm for double-clicks, extending it to higher order clicks should be fairly natural. The first thing you probably should do is to remove the CS_DBLCLKS style from your class because you want to do multiple-click management manually.

    Next, you can simply reimplement the same algorithm that the window manager uses, but take it to a higher order than just two. Let's do that. Start with a clean scratch program and add the following:

    int g_cClicks = 0;
    RECT g_rcClick;
    DWORD g_tmLastClick;
    void OnLButtonDown(HWND hwnd, BOOL fDoubleClick,
                       int x, int y, UINT keyFlags)
      POINT pt = { x, y };
      DWORD tmClick = GetMessageTime();
      if (!PtInRect(&g_rcClick, pt) ||
          tmClick - g_tmLastClick > GetDoubleClickTime()) {
        g_cClicks = 0;
      g_tmLastClick = tmClick;
      SetRect(&g_rcClick, x, y, x, y);
                  GetSystemMetrics(SM_CXDOUBLECLK) / 2,
                  GetSystemMetrics(SM_CYDOUBLECLK) / 2);
      TCHAR sz[20];
      wnsprintf(sz, 20, TEXT("%d"), g_cClicks);
      SetWindowText(hwnd, sz);
    void ResetClicks()
      g_cClicks = 0;
      SetWindowText(hwnd, TEXT("Scratch"));
    void OnActivate(HWND hwnd, UINT state, HWND, BOOL)
    void OnRButtonDown(HWND hwnd, BOOL fDoubleClick,
                       int x, int y, UINT keyFlags)
        HANDLE_MSG(hwnd, WM_LBUTTONDOWN, OnLButtonDown);
        HANDLE_MSG(hwnd, WM_ACTIVATE, OnActivate);

    [Boundary test for double-click time corrected 10:36am.]

    (Our scratch program doesn't use the CS_DBLCLKS style, so we didn't need to remove it - it wasn't there to begin with.)

    The basic idea here is simple: When a click occurs, we see if it is in the "double-click zone" and has occurred within the double-click time. If not, then we reset the consecutive click count.

    (Note that the SM_CXDOUBLECLK and SM_CYDOUBLECLK values are the width of the entire rectangle, so we cut it in half when inflating so that the rectangle extends halfway in either direction. Yes, this means that a pixel is lost if the double-click width is odd, but Windows has been careful always to set the value to an even number.)

    Next, we record the coordinates and time of the current click so the next click can compare against it.

    Finally, we react to the click by putting the consecutive click number in the title bar.

    There are some subtleties in this code. First, notice that setting g_cClicks to zero forces the next click to be treated as the first click in a series, for regardless of whether it matches the other criteria, all that will happen is that the click count increments to 1.

    Next, notice that the way we test whether the clicks occurred within the double click time was done in a manner that is not sensitive to timer tick rollover. If we had written

          tmClick > g_tmLastClick + GetDoubleClickTime()) {

    then we would fail to detect multiple clicks properly near the timer tick rollover. (Make sure you understand this.)

    Third, notice that we reset the click count when the window gains or loses activation. That way, if the user clicks, then switches away, then switches back, and then clicks again, that is not treated as a double-click. We do the same if the user clicks the right mouse button in between. (You may notice that few programs bother with quite this much subtlety.)

    Exercise: Suppose your program isn't interested in anything beyond triple-clicks. How would you change this program in a manner consistent with the way the window manager stops at double-clicks?

  • The Old New Thing

    Logical consequences of the way Windows converts single-clicks into double-clicks


    First, I'm going to refer you to the MSDN documentation on mouse clicks, since that's the starting point. I'm going to assume that you know the mechanics of how single-clicks are converted to double-clicks.

    Okay, now that you've read it, let's talk about some logical consequences of that article and what it means for the way you design your user interface.

    First, some people design their double-click action to be something unrelated to the single-click action. They want to know if they can suppress the initial WM_LBUTTONDOWN of the double-click sequence.

    Of course, you realize that that would require clairevoyance. When the mouse button goes down for the first time, the window manager doesn't know whether another click will come or not. (Heck, often the user doesn't know either!) So it spits out a WM_LBUTTONDOWN and waits for more.

    Now suppose you're a program that nevertheless wants to continue with the dubious design of having the double-click action be unrelated to the single-click action. What do you do?

    Well, one thing you could do is to do nothing on receipt of the WM_LBUTTONDOWN message aside from set a timer to fire in GetDoubleClickTime() milliseconds. [Corrected 10am.] If you get a WM_LBUTTONDBLCLK message within that time, then it was a double-click after all. If you don't, then it must have been a single-click, so you can do your single-click action (although a bit late).

    This "wait a tick" technique is also necessary if you don't have a double-click action, but the second click causes trouble in conjunction with the first click. Why is this necessary? Because many users double-click everything. Here are some examples of where the "delayed action to avoid the second click" can be seen:

    • The context menu that appears for taskbar notification icons. If the context menu appeared immediately upon the first click, then the second click would dismiss the context menu, leaving the user confused. "I clicked and something happened and then it went away." (Users don't say "I double-clicked"; they just say that they clicked. Double-click is the only thing they know how to do, so they just call it "click". For the same reason you don't say "I drove my blue car" if you have only one car.)
    • If Explorer is in one-click mode, it waits to see if there is a second click, and if so, it ignores it. Otherwise, when people double-click, they launch two copies of the program. Furthermore, if you suppress the second click but don't wait a tick, then the program they launched gets stuck behind the Explorer window, since the user clicked on Explorer after launching the program.
    • The XP style Start button ignores the second click. Otherwise, when people double-click the Start button, the first click would open the Start menu and the second click would dismiss it! (This is sometimes known as "debouncing".)

    Let's demonstrate how you might implement click delay. Start with the scratch program and add the following:

    void CALLBACK DelayedSingleClick(HWND hwnd, UINT,
                                     UINT_PTR id, DWORD)
        KillTimer(hwnd, id);
    void OnLButtonDown(HWND hwnd, BOOL fDoubleClick,
                       int x, int y, UINT keyFlags)
        if (fDoubleClick) {
            KillTimer(hwnd, 1);
        } else {
            SetTimer(hwnd, 1, GetDoubleClickTime(),
        HANDLE_MSG(hwnd, WM_LBUTTONDOWN, OnLButtonDown);
        HANDLE_MSG(hwnd, WM_LBUTTONDBLCLK, OnLButtonDown);

    Also, since we're messing with double clicks, we should turn them on:

        wc.style = CS_DBLCLKS;

    When you run this program, click and double-click in the client area. Notice that the program doesn't react to the single click until after your double-click timeout has elapsed, because it's waiting to see if you are going to continue to click a second time (and therefore double-click instead of single-click).

    Next time, we'll look at clicks beyond two.

  • The Old New Thing

    Little facts you didn't know about volcanoes


    Molten rock makes volcano glow red, reports CNN.

    Amazing the things you can learn from the news. Last year we learned that concrete is stronger once it has hardened.

  • The Old New Thing

    The procedure entry point SHCreateThreadRef could not be located...


    Some people smarter than me have been working on this problem, and here's what they figured out. First, I'll just reprint their analysis (so that people with the problem and are searching for the solution can find it), and then we can discuss it.

    Update (18 October 2005): The official version of this document is now available. Please use that version instead of following the steps below, which were based on a preliminary version of the instructions.

    If you receive an error message: "Explorer.EXE - Entry Point Not Found - The procedure entry point SHCreateThreadRef could not be located in the dynamic link library SHLWAPI.dll", this information will help resolve the issue.

    This appears to have been caused by the following sequence of events:

    1. You installed Windows XP Service Pack 2
    2. The installation of Service Pack 2 failed due to a computer crash during the installation which resulted in the automatic Service Pack Recovery process. On next boot, you should have received an error message telling you that the install failed, and that you need to go to the control panel and uninstall SP2 and then try re-installing it. This message may have been dismissed accidentally or by another individual using your computer. In any event, the Service Pack recovery process was not completed by uninstalling the service pack from the Add/Remove Programs control panel, and the system is consequently in a partially installed state which is not stable.
    3. You then installed the latest security update for Windows XP, MS04-038, KB834707. Because your system is still partially SP2, the SP2 version of this fix was downloaded and installed by Windows Update or Automatic Updates. However, the operating system files on the system are the original versions due to the SP Recovery process. This results in mismatched files causing this error.

    To recover the system, carefully perform the following steps:

    1. Boot normally and attempt to log in to your desktop. At this point you should get the error message listed above.
    2. Press Control+Alt+Delete at the same time to start the Task Manager. (If you are using classic logon, click the "Task Manager" button.) You may get additional error messages but Task Manager will eventually start.
    3. On the menu bar, select File and then New Task (Run).
    4. Type in control appwiz.cpl into the new task box and hit OK. You may get additional errors that can be ignored.
    5. The Add/Remove Control Panel should now be running. You can close Task Manager.
    6. Near the bottom of the list, find the entry titled "Windows XP Hotfix – KB834707".
    7. Click on it and click the "Remove" button. It will take some time to complete. Once the "Finish" button is visible, click on it and reboot your system. If you get messages about additional software or hotfixes installed, you can safely ignore them.

    Do NOT stop now! Your system is still in the "failed SP2 install" state. You MUST complete the SP2 uninstall, and then re-install SP2.

    1. Start the system and log in.
    2. Click on Start and then Control Panel.
    3. Click on the Add/Remove programs item.
    4. Near the bottom of the list, find the entry titled "Windows XP Service Pack 2".
    5. Click on it and remove Service Pack 2. You may get a warning about software you have installed after SP2. Make a note of it as you may need to reinstall some of them after the uninstall operation.
    6. After Service Pack 2 has been successfully removed, you should visit http://www.microsoft.com/sp2install for instructions on installing Service Pack 2. You can get SP2 from http://windowsupdate.microsoft.com.
    7. After Service Pack 2 has been successfully re-installed, you should re-visit Windows Update to get the proper version of the latest critical security updates.


    Q: I don't believe I am in the "partially installed SP2" state. Is there any way to check that?

    A: After step 7, your system should be able to log in. There are several ways to check.

    1. Open the file c:\windows\svcpack.log, and scroll to the very bottom of the file. About 10 lines from the end, you should see:
      0.xxx: Executing script \SystemRoot\sprecovr.txt
      0.xxx: In cleanup mode. System will not be rebooted.
      If you have these lines in svcpack.log, and you did not uninstall Service Pack 2 in Add/Remove Programs, you definitely have a machine in this partially installed state.
    2. Click on the Start button, then Run, and type winver, then click OK. If the version is "Version 5.1 (Build 2600.xpsp_sp2_rtm.040803-2158: Service Pack 2" then you have the correct SP2 install. If, however, it has a number that is less than 040803 after the xpsp2, such as "Build 2600.xpsp2.030422-1633 : Service Pack 2" then you definitely have a machine in the partially installed state. [Corrected typo in version numbering, 11am.]

    Notice that the default answer to every dialog box is "Cancel". The Service Pack setup program detected a problem and gave instructions on how to fix it, and yet people just ignored the message.

    The result of not fixing the problem is that you are left with a machine that is neither Service Pack 1 nor Service Pack 2 but instead is a Frankenstein monster of some files from Service Pack 1 and some files from Service Pack 2. This is hardly a good situation. Half the files are mismatched with the other half.

    There was enough of Service Pack 2 on your machine that Windows Update downloaded the Service Pack 2 version of the security patch and tried to install it. This made a bad situation worse.

    What's the moral of the story?

    First, that users ignore all error messages. Even error messages that tell how to fix the error! Second, that ignoring some error messages often leads to worse error messages. And third, that once you get into this situation, you're off in uncharted territory. And there are only dragons there.

    (These are the types of problems that are nearly impossible to debug: It will never happen in the test lab, because the test lab people know that when an error message tells you, "The install failed; you need to do XYZ to fix it," you should do it! All you can work from are descriptions from people who are having the problem, and a lot of creativity to try to guess "What happened that they aren't telling me?")

  • The Old New Thing

    Dispatching items collected from the suggestion box


    Okay, I got around to digging through the suggestion box, and today I'm going to dispatch the items that don't require much thought but seemed worthy of reply to some degree. You won't learn much of anything today.

    • Topics I try to avoid

      "I would like to hear your opinion of the programming languages you have used."
      "What, in your opinion, is the strengths and weaknesses of the difference programming languages included in Visual Studio?"
      "What things do you wish had been added to Win32 that never were?"
      "What one thing do you believe should never have been added to Win32?"

      I try to avoid pure opinion pieces on technical matters. (Especially if they aren't funny.)

      "How is Microsoft working to eliminate the CRT-based 96dpi default setting in future versions of Windows?"
      "How has the command line evolved and where is it heading?"
      "How might Win32 evolve in the next version of Windows?"

      I try to avoid speculating about the future.

      "Do Microsofties keep their checkouts on their own hard drive, or do you have a home directory on a server?"
      "Does anyone ever get fired for making a mistake that costs everyone a lot of time and money?"

      I don't feel I have the authority to discuss this.

    • Topics that are outside my realm of expertise

      "How should I store a user's credentials securely?"
      "Sometimes when we query the SID of a computer we get back a blank SID."

      You want to ask a security person, like say Michael Howard.

      "Terminal Services sessions won't span more than 1 monitor. Any workarounds or suggestions?"

      This a question for the Terminal Services team.

      "Why is it so hard to make Windows Messenger go away?"

      Try asking the Windows Messenger team.

      "Why doesn't the Office/Visual Studio team release the code for their neato UI's."
      "How do I paste pictures into a Word document?"
      "How do I position page numbers in Word?"
      "Why does the VBA ROUND function differ from Excel?"

      I am not the Office or Visual Studio team.

      "So, I'm wondering who to go to talk about COM marshalling stuff."
      "What settings is Windows saving when it says it's saving your settings at shutdown/logoff?"
      "Why are mutexes internally known in Windows NT as "mutants"?"
      "Is there any way to shutdown USB device using keyboard instead of clicking on notification icon?"
      "Can you comment on the IXmlSerializable interface..."
      "A question about the windows networking system..."
      "Is there any good reason Windows hardly ever finds the CD drive, or whatever directory you've already specified 17 times has the drivers?"
      "How about when you make some very minor change in TCP/IP settings and Windows reinstalls about 34 .dll files that are already in System32?"
      "Why does my 200GB drive show up as 131GB?"
      "How do I install Message Queueing?"
      "How do I fix the error 'Visual Basic module is corrupt'?"
      "Why does Internet Explorer lose my status bar?"

      I have no idea. I have never worked on logon/logoff or COM marshalling or USB hardware or the CLR or networking or Plug and Play or backup... (Though for the status bar question, I have a wild guess which is likely to be completely wrong. If the last window you close is a pop-up ad with no status bar, then maybe it is that "no status bar" that is the one that ends up being saved as your preference. Just a wild guess.)

      "Why can't I customize X?"

      Every customization comes at a cost. The line has to be drawn somewhere.

    • Topics not of general interest (usually "please debug my computer")

      "Why does running MS Money make these shortcuts slow down to a crawl?"

      Sounds like something you should take up with the MS Money folks.

      "What's with that taskbar bug where you click the button for one window and another pops up right before it does?"

      I've never see this.

      "Why can't I rename My Computer?"

      "My Computer" renaming works for me.

      "In my program, I get an error resource not found."
      "I'm having a problem with backing up my computer."
      "I want to grep date or timestamp of files in UNIX through shell script where date format or timestamp is like 'Jul 9' or 'Jul 12'. In this where day is of single digit the space between month and day is 2 characters, but where day is of 2 digits the space is of 1 character."
      "I have a form that needs a date field to populate upon opening the file. Then the date will be used to generate a number for Tracking purposes. Then when the file is saved and e-mailed to the next person the date and number must remain fixed. What is the best way to set this up?"

      These questions aren't of general interest. One of them isn't even about Windows, and I don't understand that last one, but please, don't explain.

      "Help me get my iPod working."
      "Please translate this into German for me."
      "Please forward me some information as well as a demo and some samples of Blogs." (from a Fortune 500 company no less)
      "How do I take a screenshot of my Powerbook G4?"

      I am at a loss as to how I should respond to these requests.

    • Short answers

      "At one point with tab controls it was documented to let you set the color of the tab. Why was it removed?"

      You could do that? News to me.

      "Why do you make it so hard to properly create a replacement shell?"

      It's not that anybody makes it hard; rather that nobody goes out of their way to make it easy.

      "Why is explorer.exe monolithic? Why wasn't there a desktop.exe, taskbar.exe, etc?"

      Processes are expensive.

      "If a program has a dialog box open, why does Alt+Tab show the dialog box title, but Task Manager shows the application title?"

      Because Alt+Tab is for switching among windows. But Task Manager is for switching among applications.

      "What is the history of the "X" button?"

      Nothing earth-shattering. Studies showed that people didn't know how to exit programs, so some people sat down and tried to work out some universal symbol that would clearly indicate "Close".

      "Why can't you change the bitmap on the Start menu?"

      It's branding.

      "Why is it so hard to write a program that does gross things to Explorer?"

      Allowing other programs do gross things to Explorer wasn't on its list of design goals.

    Other entries require more thought. Each non-code entry takes me a half hour or so, more if I have to do research (if somebody asks me a question outside my area of expertise and I feel like hunting for the answer), and the code entries require an hour and a half or more. Some of them take days. I think I spent three weeks on the dialog box template series, and about as much on the context menus. (And there are plenty of other series in various stages of completion.)

    I don't get paid to do this. It's just a hobby. It's frustrating when people order you to spend more time on your hobby. And then when you decide it's too much and start closing comments to try to get some of your life back, people call you a coward and assume that you stopped for some nefarious reason. (Like when they say, "Look, Raymond closed comments as soon as people started asking embarrassing questions." But the comment timestamps prove otherwise. I closed comments weeks or months later.)

Page 374 of 429 (4,285 items) «372373374375376»