diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-03 21:49:35 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-03 21:49:35 +0000 |
| commit | 424da766b76ba572cc9a93c4dd03736dace04d80 (patch) | |
| tree | 899f6ceec730bd5e1305f1c8e59ce13e21cfb048 /src | |
| parent | 3c18a66ef601d90f08ea622021cb02bafab1601b (diff) | |
| parent | e63fcfff1ae24dfff8aca6581badb51a06ddae28 (diff) | |
merge: !10 fix/small-fixes into deploy/all-2026-08-24
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.cpp | 27 | ||||
| -rw-r--r-- | src/readme.txt | 2 | ||||
| -rw-r--r-- | src/rpc.cpp | 14 | ||||
| -rw-r--r-- | src/util.cpp | 99 | ||||
| -rw-r--r-- | src/util.h | 22 |
5 files changed, 142 insertions, 22 deletions
diff --git a/src/main.cpp b/src/main.cpp index 981509c..dded83a 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
| @@ -2679,7 +2679,18 @@ bool LoadBlockIndex(bool fAllowNew) | |||
| 2679 | // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B) | 2679 | // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B) |
| 2680 | // vMerkleTree: 4a5e1e | 2680 | // vMerkleTree: 4a5e1e |
| 2681 | 2681 | ||
| 2682 | // Bitflash genesis block -- own header (fair launch, 2026 stable relaunch) | 2682 | // Bitflash genesis block -- own header (fair launch) |
| 2683 | // | ||
| 2684 | // Do not change pszTimestamp, the scriptPubKey, nTime or nNonce. They | ||
| 2685 | // are the inputs to the genesis hash, and the genesis hash is | ||
| 2686 | // consensus. Changing one on its own trips the assert below, which is | ||
| 2687 | // loud and reverts cleanly; changing one and regenerating | ||
| 2688 | // GENESIS_NONCE and hashGenesisBlock to match -- which is exactly what | ||
| 2689 | // the re-mining branch below prints out and invites -- gives a node | ||
| 2690 | // that starts without complaint, builds its own chain from height 0, | ||
| 2691 | // and rejects every real peer as being on a different chain. That one | ||
| 2692 | // fails silently, and the damage scales with how many people run the | ||
| 2693 | // "fixed" build. | ||
| 2683 | char* pszTimestamp = "Bitflash 24/Jul/2026 Fair launch: one CPU one vote, no premine"; | 2694 | char* pszTimestamp = "Bitflash 24/Jul/2026 Fair launch: one CPU one vote, no premine"; |
| 2684 | CTransaction txNew; | 2695 | CTransaction txNew; |
| 2685 | txNew.vin.resize(1); | 2696 | txNew.vin.resize(1); |
| @@ -2692,7 +2703,14 @@ bool LoadBlockIndex(bool fAllowNew) | |||
| 2692 | block.hashPrevBlock = 0; | 2703 | block.hashPrevBlock = 0; |
| 2693 | block.hashMerkleRoot = block.BuildMerkleTree(); | 2704 | block.hashMerkleRoot = block.BuildMerkleTree(); |
| 2694 | block.nVersion = 1; | 2705 | block.nVersion = 1; |
| 2695 | block.nTime = 1753315200; // 2026-07-24 00:00:00 UTC (stable relaunch) | 2706 | // 1753315200 is 2025-07-24T00:00:00Z. The headline in pszTimestamp |
| 2707 | // above reads 2026 and does not match it. This is a known discrepancy | ||
| 2708 | // and not a typo in the constant: the value is only consensus-critical | ||
| 2709 | // as an input to the genesis hash, the block hashes to | ||
| 2710 | // hashGenesisBlock with it, and every node on the network agrees. The | ||
| 2711 | // mismatch is in the launched chain and cannot be corrected -- see the | ||
| 2712 | // warning above before deciding otherwise. | ||
| 2713 | block.nTime = 1753315200; | ||
| 2696 | block.nBits = 0x1f0fffff; // = bnProofOfWorkLimit (Bitflash RandomX floor) | 2714 | block.nBits = 0x1f0fffff; // = bnProofOfWorkLimit (Bitflash RandomX floor) |
| 2697 | block.nNonce = GENESIS_NONCE; | 2715 | block.nNonce = GENESIS_NONCE; |
| 2698 | 2716 | ||
| @@ -2719,6 +2737,11 @@ bool LoadBlockIndex(bool fAllowNew) | |||
| 2719 | printf("powhash = %s\n", block.GetPoWHash().ToString().c_str()); | 2737 | printf("powhash = %s\n", block.GetPoWHash().ToString().c_str()); |
| 2720 | printf("hash = %s\n", block.GetHash().ToString().c_str()); | 2738 | printf("hash = %s\n", block.GetHash().ToString().c_str()); |
| 2721 | printf("merkle = %s\n", block.hashMerkleRoot.ToString().c_str()); | 2739 | printf("merkle = %s\n", block.hashMerkleRoot.ToString().c_str()); |
| 2740 | printf("----------------------------------------\n"); | ||
| 2741 | printf("These constants bring up a NEW chain. Do not paste them\n"); | ||
| 2742 | printf("into a build for this network to make an edited nTime or\n"); | ||
| 2743 | printf("pszTimestamp fit -- a node whose genesis differs starts\n"); | ||
| 2744 | printf("cleanly on a chain of its own and rejects every real peer.\n"); | ||
| 2722 | printf("========================================\n"); | 2745 | printf("========================================\n"); |
| 2723 | } | 2746 | } |
| 2724 | 2747 | ||
diff --git a/src/readme.txt b/src/readme.txt index 752be66..7490a9a 100644 --- a/src/readme.txt +++ b/src/readme.txt | |||
| @@ -16,7 +16,7 @@ This product includes software developed by the OpenSSL Project. | |||
| 16 | Differences from the original Bitcoin | 16 | Differences from the original Bitcoin |
| 17 | ------------------------------------- | 17 | ------------------------------------- |
| 18 | - Own genesis block (22/Jul/2026 headline, independent chain) | 18 | - Own genesis block (22/Jul/2026 headline, independent chain) |
| 19 | - Own magic bytes (0xbf 0x1a 0x5c 0xfd) and P2P port 8433 | 19 | - Own magic bytes (0xbf 0x20 0x5c 0xfd) and P2P port 8433 |
| 20 | - Addresses start with "B" (ADDRESSVERSION = 25) | 20 | - Addresses start with "B" (ADDRESSVERSION = 25) |
| 21 | - Consensus fixes applied: value overflow (CVE-2010-5139), duplicate inputs, | 21 | - Consensus fixes applied: value overflow (CVE-2010-5139), duplicate inputs, |
| 22 | money range checks (MoneyRange) | 22 | money range checks (MoneyRange) |
diff --git a/src/rpc.cpp b/src/rpc.cpp index 3cee687..bd5fd8b 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp | |||
| @@ -1732,7 +1732,9 @@ static void FlushMaturePayouts() | |||
| 1732 | } | 1732 | } |
| 1733 | int64 total = 0; | 1733 | int64 total = 0; |
| 1734 | for (auto& kv : it->amounts) total += kv.second; | 1734 | for (auto& kv : it->amounts) total += kv.second; |
| 1735 | LogPrint("payout", "[payout] block %d (%s) was orphaned -- voiding %zu" | 1735 | // Always: one line per voided entry, never a collapsed summary. |
| 1736 | // A reorg voids these in a burst from this one source line. | ||
| 1737 | LogPrintAlways("payout", "[payout] block %d (%s) was orphaned -- voiding %zu" | ||
| 1736 | " payment(s) totalling %s\n", | 1738 | " payment(s) totalling %s\n", |
| 1737 | it->blockHeight, it->blockHash.ToString().substr(0, 14).c_str(), | 1739 | it->blockHeight, it->blockHash.ToString().substr(0, 14).c_str(), |
| 1738 | it->amounts.size(), FormatMoney(total).c_str()); | 1740 | it->amounts.size(), FormatMoney(total).c_str()); |
| @@ -1931,7 +1933,7 @@ static void RecordPayoutTxid(const PayingRecord& r, const std::string& txid) | |||
| 1931 | // disagreeing. | 1933 | // disagreeing. |
| 1932 | static void ForfeitPayoutRecipient(const PayingRecord& r, const std::string& reason) | 1934 | static void ForfeitPayoutRecipient(const PayingRecord& r, const std::string& reason) |
| 1933 | { | 1935 | { |
| 1934 | LogPrint("payout", "[payout] writing off %s owed to %s for block %d: %s\n", | 1936 | LogPrintAlways("payout", "[payout] writing off %s owed to %s for block %d: %s\n", |
| 1935 | FormatMoney(r.amount).c_str(), r.address.c_str(), r.blockHeight, | 1937 | FormatMoney(r.amount).c_str(), r.address.c_str(), r.blockHeight, |
| 1936 | reason.c_str()); | 1938 | reason.c_str()); |
| 1937 | VersionedBlob blob; | 1939 | VersionedBlob blob; |
| @@ -2036,7 +2038,7 @@ static void PayoutThreadFn(void*) | |||
| 2036 | // funding block can land at any point in that window. Upstream has | 2038 | // funding block can land at any point in that window. Upstream has |
| 2037 | // no equivalent -- it decides once, when the entry matures. | 2039 | // no equivalent -- it decides once, when the entry matures. |
| 2038 | if (!PayoutBlockStillOnChain(r.blockHash)) { | 2040 | if (!PayoutBlockStillOnChain(r.blockHash)) { |
| 2039 | LogPrint("payout", "[payout] block %d (%s) orphaned while queued" | 2041 | LogPrintAlways("payout", "[payout] block %d (%s) orphaned while queued" |
| 2040 | " -- dropping %s to %s\n", | 2042 | " -- dropping %s to %s\n", |
| 2041 | r.blockHeight, r.blockHash.ToString().substr(0, 14).c_str(), | 2043 | r.blockHeight, r.blockHash.ToString().substr(0, 14).c_str(), |
| 2042 | FormatMoney(r.amount).c_str(), r.address.c_str()); | 2044 | FormatMoney(r.amount).c_str(), r.address.c_str()); |
| @@ -2080,7 +2082,7 @@ static void PayoutThreadFn(void*) | |||
| 2080 | const std::string reason = | 2082 | const std::string reason = |
| 2081 | strprintf("'%s' is not a valid address, so no transaction" | 2083 | strprintf("'%s' is not a valid address, so no transaction" |
| 2082 | " can pay it", r.address.c_str()); | 2084 | " can pay it", r.address.c_str()); |
| 2083 | LogPrint("payout", "[payout] SKIPPED bad address '%s' --" | 2085 | LogPrintAlways("payout", "[payout] SKIPPED bad address '%s' --" |
| 2084 | " %s owed for block %d cannot be paid\n", | 2086 | " %s owed for block %d cannot be paid\n", |
| 2085 | r.address.c_str(), FormatMoney(r.amount).c_str(), | 2087 | r.address.c_str(), FormatMoney(r.amount).c_str(), |
| 2086 | r.blockHeight); | 2088 | r.blockHeight); |
| @@ -2205,7 +2207,7 @@ static void PayoutThreadFn(void*) | |||
| 2205 | // described by at least one of them at every instant. | 2207 | // described by at least one of them at every instant. |
| 2206 | RecordPayoutTxid(r, wtx.GetHash().GetHex()); | 2208 | RecordPayoutTxid(r, wtx.GetHash().GetHex()); |
| 2207 | ForgetPayment(r); | 2209 | ForgetPayment(r); |
| 2208 | LogPrint("payout", "[payout] paid %s to %s (tx %s)\n", | 2210 | LogPrintAlways("payout", "[payout] paid %s to %s (tx %s)\n", |
| 2209 | FormatMoney(r.amount).c_str(), r.address.c_str(), | 2211 | FormatMoney(r.amount).c_str(), r.address.c_str(), |
| 2210 | wtx.GetHash().ToString().substr(0, 12).c_str()); | 2212 | wtx.GetHash().ToString().substr(0, 12).c_str()); |
| 2211 | } else { | 2213 | } else { |
| @@ -2263,7 +2265,7 @@ static void PayoutThreadFn(void*) | |||
| 2263 | // going away must not turn the log into one line a minute | 2265 | // going away must not turn the log into one line a minute |
| 2264 | // forever, and must not go quiet either. | 2266 | // forever, and must not go quiet either. |
| 2265 | if (r.retries <= 5 || r.retries % 60 == 0) | 2267 | if (r.retries <= 5 || r.retries % 60 == 0) |
| 2266 | LogPrint("payout", "[payout] FAILED to pay %s to %s" | 2268 | LogPrintAlways("payout", "[payout] FAILED to pay %s to %s" |
| 2267 | " (%s spendable, wallet unlocked, block %d on" | 2269 | " (%s spendable, wallet unlocked, block %d on" |
| 2268 | " the main chain) -- attempt %d, retrying in" | 2270 | " the main chain) -- attempt %d, retrying in" |
| 2269 | " 60s; this debt stands until it is paid\n", | 2271 | " 60s; this debt stands until it is paid\n", |
diff --git a/src/util.cpp b/src/util.cpp index 60142be..ab29178 100644 --- a/src/util.cpp +++ b/src/util.cpp | |||
| @@ -65,6 +65,61 @@ struct LogDedupState { | |||
| 65 | static std::map<std::string, LogDedupState> g_logDedup; | 65 | static std::map<std::string, LogDedupState> g_logDedup; |
| 66 | static CCriticalSection cs_logDedup; | 66 | static CCriticalSection cs_logDedup; |
| 67 | 67 | ||
| 68 | // A pending "(N similar)" summary, lifted out of the table while the lock is | ||
| 69 | // held and written after it is released. | ||
| 70 | struct LogDedupFlush { | ||
| 71 | std::string category; | ||
| 72 | int suppressed; | ||
| 73 | int64 elapsed; // how long the window that counted them ran | ||
| 74 | std::string lastMsg; | ||
| 75 | }; | ||
| 76 | |||
| 77 | static int64 g_logDedupLastSweep = 0; | ||
| 78 | |||
| 79 | static void EmitDedupSummary(const LogDedupFlush& f) | ||
| 80 | { | ||
| 81 | OutputDebugStringF("[%s] (%d similar in %llds, last: %s)\n", | ||
| 82 | f.category.c_str(), f.suppressed, | ||
| 83 | (long long)f.elapsed, f.lastMsg.c_str()); | ||
| 84 | } | ||
| 85 | |||
| 86 | // A window is otherwise only closed out when its own call site fires again, so | ||
| 87 | // a burst that simply stops -- 100 ledger entries voided in one pass and then | ||
| 88 | // silence -- leaves its count sitting in the table and the summary is never | ||
| 89 | // written at all. That is how 99 of 100 voids went unreported, with no "(N | ||
| 90 | // similar)" line to say anything was missing (issue #3). Sweep every expired | ||
| 91 | // window instead, so a finished burst is summarised as soon as anything at all | ||
| 92 | // is logged. | ||
| 93 | // | ||
| 94 | // Expired entries are erased rather than reopened: the window is over, so there | ||
| 95 | // is no state left worth keeping, and the next call from that site then takes | ||
| 96 | // the "first time" path and prints directly -- exactly what the rollover branch | ||
| 97 | // below would have done. It also stops the table growing without bound. | ||
| 98 | // | ||
| 99 | // Caller holds cs_logDedup; the summaries are written after it is released. | ||
| 100 | static void SweepExpiredWindowsLocked(int64 now, std::vector<LogDedupFlush>& out) | ||
| 101 | { | ||
| 102 | for (std::map<std::string, LogDedupState>::iterator it = g_logDedup.begin(); | ||
| 103 | it != g_logDedup.end(); ) | ||
| 104 | { | ||
| 105 | if (now - it->second.windowStart < DEDUP_WINDOW_SECS) | ||
| 106 | { | ||
| 107 | ++it; | ||
| 108 | continue; | ||
| 109 | } | ||
| 110 | if (it->second.suppressed > 0) | ||
| 111 | { | ||
| 112 | LogDedupFlush f; | ||
| 113 | f.category = it->first.substr(0, it->first.find('|')); | ||
| 114 | f.suppressed = it->second.suppressed; | ||
| 115 | f.elapsed = now - it->second.windowStart; | ||
| 116 | f.lastMsg = it->second.lastMsg; | ||
| 117 | out.push_back(f); | ||
| 118 | } | ||
| 119 | g_logDedup.erase(it++); | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 68 | void LogPrintDedup(const char* category, const char* file, int line, const std::string& msgIn) | 123 | void LogPrintDedup(const char* category, const char* file, int line, const std::string& msgIn) |
| 69 | { | 124 | { |
| 70 | // Call sites follow the existing printf() convention of ending their | 125 | // Call sites follow the existing printf() convention of ending their |
| @@ -79,11 +134,19 @@ void LogPrintDedup(const char* category, const char* file, int line, const std:: | |||
| 79 | 134 | ||
| 80 | int64 now = GetTime(); | 135 | int64 now = GetTime(); |
| 81 | bool fEmitNow = false; | 136 | bool fEmitNow = false; |
| 82 | int suppressedToFlush = 0; | 137 | std::vector<LogDedupFlush> flushes; |
| 83 | std::string flushedMsg; | ||
| 84 | 138 | ||
| 85 | CRITICAL_BLOCK(cs_logDedup) | 139 | CRITICAL_BLOCK(cs_logDedup) |
| 86 | { | 140 | { |
| 141 | // At most once a second. The table holds one entry per call site that | ||
| 142 | // has fired in the last few seconds, so the walk is short, but it must | ||
| 143 | // not run once per line of a burst. | ||
| 144 | if (now != g_logDedupLastSweep) | ||
| 145 | { | ||
| 146 | g_logDedupLastSweep = now; | ||
| 147 | SweepExpiredWindowsLocked(now, flushes); | ||
| 148 | } | ||
| 149 | |||
| 87 | std::map<std::string, LogDedupState>::iterator it = g_logDedup.find(key); | 150 | std::map<std::string, LogDedupState>::iterator it = g_logDedup.find(key); |
| 88 | if (it == g_logDedup.end()) | 151 | if (it == g_logDedup.end()) |
| 89 | { | 152 | { |
| @@ -94,10 +157,18 @@ void LogPrintDedup(const char* category, const char* file, int line, const std:: | |||
| 94 | } | 157 | } |
| 95 | else if (now - it->second.windowStart >= DEDUP_WINDOW_SECS) | 158 | else if (now - it->second.windowStart >= DEDUP_WINDOW_SECS) |
| 96 | { | 159 | { |
| 97 | // Window rolled over: flush what was suppressed during it (if | 160 | // Window rolled over within the same second as the last sweep: |
| 98 | // anything), then start a new window with this call printed. | 161 | // flush what was suppressed during it (if anything), then start a |
| 99 | suppressedToFlush = it->second.suppressed; | 162 | // new window with this call printed. |
| 100 | flushedMsg = it->second.lastMsg; | 163 | if (it->second.suppressed > 0) |
| 164 | { | ||
| 165 | LogDedupFlush f; | ||
| 166 | f.category = category; | ||
| 167 | f.suppressed = it->second.suppressed; | ||
| 168 | f.elapsed = now - it->second.windowStart; | ||
| 169 | f.lastMsg = it->second.lastMsg; | ||
| 170 | flushes.push_back(f); | ||
| 171 | } | ||
| 101 | it->second.windowStart = now; | 172 | it->second.windowStart = now; |
| 102 | it->second.suppressed = 0; | 173 | it->second.suppressed = 0; |
| 103 | it->second.lastMsg = msg; | 174 | it->second.lastMsg = msg; |
| @@ -112,13 +183,23 @@ void LogPrintDedup(const char* category, const char* file, int line, const std:: | |||
| 112 | } | 183 | } |
| 113 | } | 184 | } |
| 114 | 185 | ||
| 115 | if (suppressedToFlush > 0) | 186 | for (size_t i = 0; i < flushes.size(); i++) |
| 116 | OutputDebugStringF("[%s] (%d similar in %llds, last: %s)\n", | 187 | EmitDedupSummary(flushes[i]); |
| 117 | category, suppressedToFlush, (long long)DEDUP_WINDOW_SECS, flushedMsg.c_str()); | ||
| 118 | if (fEmitNow) | 188 | if (fEmitNow) |
| 119 | OutputDebugStringF("[%s] %s\n", category, msg.c_str()); | 189 | OutputDebugStringF("[%s] %s\n", category, msg.c_str()); |
| 120 | } | 190 | } |
| 121 | 191 | ||
| 192 | // LogPrintAlways() -- see the doc comment in util.h. No window, no counting, | ||
| 193 | // no table entry: every call writes its own line. | ||
| 194 | void LogPrintNoCollapse(const char* category, const std::string& msgIn) | ||
| 195 | { | ||
| 196 | std::string msg = msgIn; | ||
| 197 | while (!msg.empty() && (msg.back() == '\n' || msg.back() == '\r')) | ||
| 198 | msg.pop_back(); | ||
| 199 | |||
| 200 | OutputDebugStringF("[%s] %s\n", category, msg.c_str()); | ||
| 201 | } | ||
| 202 | |||
| 122 | 203 | ||
| 123 | 204 | ||
| 124 | 205 | ||
| @@ -115,15 +115,29 @@ extern int64 nMaxDebugFileSize; | |||
| 115 | // 2. Burst collapse: repeated LogPrint() calls from the *same source line* | 115 | // 2. Burst collapse: repeated LogPrint() calls from the *same source line* |
| 116 | // only emit one line per DEDUP_WINDOW_SECS; if more calls land inside | 116 | // only emit one line per DEDUP_WINDOW_SECS; if more calls land inside |
| 117 | // that window they're counted instead of reprinted, and the count is | 117 | // that window they're counted instead of reprinted, and the count is |
| 118 | // flushed as a single "(xN similar in Ws)" line the moment the window | 118 | // flushed as a single "(xN similar in Ws)" line once the window expires |
| 119 | // rolls over. A loop discovering 500 peers by address now produces a | 119 | // -- whether or not that call site ever fires again. A loop discovering |
| 120 | // small, bounded number of lines instead of 500 -- log growth is capped | 120 | // 500 peers by address now produces a small, bounded number of lines |
| 121 | // by elapsed time, not by how many similar events happen to fire. | 121 | // instead of 500 -- log growth is capped by elapsed time, not by how |
| 122 | // many similar events happen to fire. | ||
| 123 | // | ||
| 124 | // Use LogPrintAlways() instead for anything that reports an irreversible | ||
| 125 | // change to money: an obligation voided, a payment sent, a payment that | ||
| 126 | // failed. One line standing in for an unknown number of identical events is | ||
| 127 | // a tidy report of peer chatter and a false report of a ledger. A reorg | ||
| 128 | // voiding 100 entries hits one source line 100 times inside one window; | ||
| 129 | // under LogPrint() that read as a single block losing a single 49.49 BTF | ||
| 130 | // payment, while 4,950 BTF of obligations were actually destroyed, and the | ||
| 131 | // discrepancy was only found by counting entries in pending_payouts.json | ||
| 132 | // (issue #3). Bounded log growth is not worth that ambiguity here. | ||
| 122 | // --------------------------------------------------------------------------- | 133 | // --------------------------------------------------------------------------- |
| 123 | bool LogAcceptsCategory(const char* category); | 134 | bool LogAcceptsCategory(const char* category); |
| 124 | void LogPrintDedup(const char* category, const char* file, int line, const std::string& msg); | 135 | void LogPrintDedup(const char* category, const char* file, int line, const std::string& msg); |
| 136 | void LogPrintNoCollapse(const char* category, const std::string& msg); | ||
| 125 | #define LogPrint(category, ...) \ | 137 | #define LogPrint(category, ...) \ |
| 126 | do { if (LogAcceptsCategory(category)) LogPrintDedup((category), __FILE__, __LINE__, strprintf(__VA_ARGS__)); } while (0) | 138 | do { if (LogAcceptsCategory(category)) LogPrintDedup((category), __FILE__, __LINE__, strprintf(__VA_ARGS__)); } while (0) |
| 139 | #define LogPrintAlways(category, ...) \ | ||
| 140 | do { if (LogAcceptsCategory(category)) LogPrintNoCollapse((category), strprintf(__VA_ARGS__)); } while (0) | ||
| 127 | 141 | ||
| 128 | // Every format string in this project goes through one of the four functions | 142 | // Every format string in this project goes through one of the four functions |
| 129 | // below -- `printf` itself is remapped to OutputDebugStringF just above, and | 143 | // below -- `printf` itself is remapped to OutputDebugStringF just above, and |