๐Ÿก


  1. July 14, 2026
    1. ๐Ÿ”— New Music Releases Saliva - Longshot rss

      Saliva - a new release is available:

      • 2026-07-14: Longshot (Single)

      Amazon: Canada | Deutschland | France | United Kingdom | United States

      Visit muspy for more information.

  2. July 13, 2026
    1. ๐Ÿ”— kieler/elkjs 0.12.0 release

      add files for release 0.12.0

    2. ๐Ÿ”— r/reverseengineering /r/ReverseEngineering's Weekly Questions Thread rss

      To reduce the amount of noise from questions, we have disabled self-posts in favor of a unified questions thread every week. Feel free to ask any question about reverse engineering here. If your question is about how to use a specific tool, or is specific to some particular target, you will have better luck on the Reverse Engineering StackExchange. See also /r/AskReverseEngineering.

      submitted by /u/AutoModerator
      [link] [comments]

    3. ๐Ÿ”— r/reverseengineering LIEF v1.0.0: Brand-new Runtime API and DWARF/PDB -> C/C++ generation! rss
    4. ๐Ÿ”— openonion/connectonion Release v1.2.0 release

      Release v1.2.0

      Highlights

      co browser is now a multi-agent browser CLI : several AI agents can share one persistent, logged-in browser without stepping on each other's pages โ€” each task in its own tab, with a contention guard that teaches a colliding agent the tab lifecycle instead of silently corrupting a page. The daemon layer was hardened against every startup/shutdown race we could find (and reproduce), and agents can now be interrupted gracefully mid-task.

      What's Changed

      โœจ Features

      • co browser multi-agent tabs (#195): -t/--tab targeting for direct functions and do; tab open / ls / close lifecycle; per-tab ownership claims with a 120s guard window; loud exit-4 teaching errors on contention; wire-v1 JSON envelope (quote-safe caller/tab names); structured exit codes 0/1/2/3/4
      • Graceful stop (#188): a client INTERRUPT is honored at the iteration boundary via the existing stop_signal
      • Hosted-session safety + Patchright stealth (#186, #189): per-session tabs never tear down each other's context; patchright pinned to 1.61.2 with a stealth-driver integrity check
      • Outlook token rotation (#187): refresh tokens on every use and persist the rotation

      ๐Ÿ› Bug Fixes

      • Daemon startup race (#197): two terminals' simultaneous first commands could fork two daemons (the loser unlinked the winner's live socket โ€” reproduced in practice). The daemon now holds a lifetime flock across probe-and-bind; a pid file distinguishes a busy daemon from a stale socket, so a long do can never be mistaken for a dead daemon
      • Ownership cleanup: releasing a tab drops its page, registration, claim, and remembered URL together โ€” a reused tab name starts blank, never on the previous owner's page
      • A typo'd command no longer acquires a 120s tab claim; close_tab() without an argument closes the caller's own tab, never the shared main tab
      • Clients tell "daemon busy" apart from "daemon did not start", and fail fast on a dead daemon's stale socket

      ๐Ÿ“š Documentation

      • New docs/co-browser.md: quick start, tab lifecycle, contention model, exit-code contract, everyday-function examples, do guidance, visible-vs-headless, troubleshooting
      • Managed key onboarding clarified (#159)

      Installation

      pip install --upgrade connectonion
      

      Breaking Changes

      None.

      Full Changelog : v1.1.0...v1.2.0

    5. ๐Ÿ”— Rust Blog crates.io: development update rss

      Another six months have passed since our last development update, and the crates.io team has been busy. Here's a summary of the most notable changes and improvements made to crates.io since then.

      Source Code Viewer Crate pages now have a "Code" tab that lets you browse the contents of published crate versions directly on crates.io. This shows you the exact files that cargo downloads when you add a crate as a dependency, which might differ from the linked repository. This makes it much easier to audit your dependencies, including files that never appear in the repository, like the normalized Cargo.toml files that cargo generates. The viewer comes with a file tree sidebar with search functionality, syntax highlighting, and GitHub-style line selection, where clicking or dragging line numbers produces shareable #L10-L20 URLs. Under the hood, the server now builds a zip file for every published version. Since the .crate files that cargo consumes are gzipped tarballs without random access support, a background job re-packs each of them into a seekable zip archive plus a JSON manifest describing the contained files. Both are served from our static CDN. The frontend then fetches only the manifest and loads each file on demand with an HTTP range request. Because of this architecture, browsing crate sources essentially adds no load on the crates.io API servers. Existing crate versions have been backfilled, so this works for old releases too. The rendering library behind the code viewer is a diff renderer at heart, and that's no accident: a version-to-version diff viewer built on the same infrastructure is currently in the works. This will allow you to review exactly what changed between two published versions, right on crates.io. Stay tuned! Untangling crates.io Accounts from GitHub At the end of May, the crates.io team accepted . Crates.io accounts always

      have been tightly coupled to GitHub: signing in means "Log in with GitHub", and your crates.io identity is your GitHub username. The RFC changes that. It introduces usernames that are native to crates.io and independent of linked GitHub accounts, as a prerequisite for eventually supporting login via other identity providers.

      The implementation of crates.io usernames has started, but there is still a lot left to do, most visibly the ability to change your crates.io username. After that is complete, there will be future RFCs and implementation for signing in with identity providers other than GitHub. Since all of this touches authentication and account security, we are deliberately taking it slow and rolling these changes out in small, carefully reviewed steps.

      Advisories and Suggestions

      In our January update we introduced the "Security" tab, which shows security advisories from the RustSec database. We have since taken this integration one step further: crates that RustSec has flagged as unmaintained now show a warning banner directly on their crate pages, linking to the corresponding advisory for details and possible alternatives. Thanks to Dirkjan Ochtman for implementing this feature!

      Unmaintained warning banner on the ansi_term crate page

      Related to this, some popular crates have been largely absorbed into the Rust standard library over the years, like lazy_static, which has been superseded by std::sync::LazyLock since Rust 1.80. Crate pages of such crates now show a friendly "You might not need this dependency" banner describing the standard library replacement, and superseded crates in dependency lists get a small light bulb icon with a similar hint.

      "You might not need this dependency" banner on the lazy_static crate
page

      The dataset behind this feature lives in the new rust-lang/std-replacement- data repository, together with a documented inclusion policy: standard library replacements only, every entry must cite the stable std, core, or alloc API and Rust version, and crate maintainers get a notice-and-comment window before an entry is added. New entries can be proposed upstream and can benefit other tools too.

      Ferris

      The most delightful change of this cycle: the Ferris on our error pages now follows your mouse cursor with its eyes:

      Ferris' eyes following the mouse cursor on the error
page

      Getting a 404 error on crates.io is now slightly less sad.

      Svelte Frontend Migration Completed

      In our January update, we announced that we were experimenting with porting the crates.io frontend from Ember.js to Svelte. This experiment has concluded successfully: the new frontend reached feature parity, went through a public testing phase in April, became the default at the beginning of May, and the Ember.js app has been removed from our repository.

      We designed this change to be invisible for our users, since the new frontend is a 1:1 port of the previous design and functionality. For the team and our contributors, however, it is a big deal: the frontend is now built on a more modern framework, which should make it easier for new contributors to get started. It also allows us to iterate faster, as the source code viewer above demonstrates.

      We want to thank the Ember.js team for a framework that served crates.io well for many years, and the Svelte team for making the transition so enjoyable.

      Miscellaneous

      These were some of the more visible changes to crates.io over the past six months, but a lot has happened "under the hood" as well:

      • Search performance : Relevance-sorted search queries previously ranked every crate matching the query, which could take 1-2 seconds for short or common search terms. Ranking is now bounded to the 1,000 matching crates with the highest recent download counts.

      • Reverse dependencies performance : The reverse dependencies endpoint no longer recomputes the full dependent set on every request. It is now served from a precomputed table kept in sync by database triggers, turning an expensive join into a bounded index scan and greatly reducing the chance of getting a timeout error.

      • New ARCHITECTURE.md : If you've ever wondered how crates.io actually works, our ARCHITECTURE.md document got a complete rewrite. It is now organized around the high-level systems that make up crates.io and how they fit together, and includes walkthroughs of what happens when you run cargo publish, why a typical crate download never touches our API servers, and how download counts are derived from CDN access logs.

      • Definition lists : READMEs now render Markdown definition lists, a widely used Markdown extension. Our markdown renderer comrak already supported them, the extension just wasn't enabled yet. Thanks to @mistaste for this contribution!

      • CDN cache tags : Files uploaded to our static CDN now carry cache-tag metadata, allowing us to invalidate all cached files of a crate or a specific release in a single operation, instead of issuing one invalidation per file URL.

      • Caching improvements : We removed a global Vary: Cookie response header that was preventing our CDNs from caching public API responses and frontend assets effectively. Per-user responses now use Cache-Control: no-store instead, resulting in better cache hit rates at the CDN edge.

      • Accessibility : We have made crates.io friendlier to screen readers: decorative icons are now hidden from the accessibility tree, heading hierarchies have been fixed, and lists are marked up as proper lists. ARIA snapshot tests now ensure that regressions can't slip in unnoticed. We plan to continue to improve crates.io accessibility over the coming months.

      • Git index performance : The background worker's local clone of the git index is now a bare and shallow repository, eliminating roughly 250,000 checked-out files and the full commit history from its disk, improving its performance as we see increased rates of crate publication. The periodic index squashing now goes through the GitHub API instead of generating large git packs locally, which had previously caused out-of-memory failures on the production worker.

      Feedback

      We hope you enjoyed this update on the development of crates.io. If you have any feedback or questions, please let us know on Zulip or GitHub. We are always happy to hear from you and are looking forward to your feedback!

  3. July 12, 2026
    1. ๐Ÿ”— r/reverseengineering Vulnerability in Realtek driver allows DMA controller abuse from user mode with no additional hardware or driver rss
    2. ๐Ÿ”— r/reverseengineering Hand-crafted 974-byte Android 14 APK (API 34) โ€” Bypassing build-tool bloat by exploiting PackageInstaller structure. rss
    3. ๐Ÿ”— smol-machines/smolvm smolvm v1.5.2 release

      What's Changed

      • Preserve sparseness when copying the storage template during pack creation by @BinSquare in #604
      • cuda: llama.cpp + CUDA 13, correctness hardening, cuBLAS BLAS ops, and remote latency/coldstart cuts by @BinSquare in #607
      • Bump the workspace to 1.5.2 for the storage-template sparse-copy fix by @BinSquare in #608

      Full Changelog : v1.5.1...v1.5.2

    4. ๐Ÿ”— r/reverseengineering Analysis of the Recent HTTP.sys 9.8 RCE: Kernel-Level Integer Overflow & WinDbg Reproduction rss
    5. ๐Ÿ”— r/reverseengineering MaFrida v0.3.0 is out! rss
    6. ๐Ÿ”— r/reverseengineering Pocket Disasm โ€” parallel headless binary analysis through one IDALib MCP endpoint rss
    7. ๐Ÿ”— r/reverseengineering APKXHunter v1.0.0 โ€” an open-source Android Static Analysis Framework rss
    8. ๐Ÿ”— smol-machines/smolvm smolvm v1.5.1 release

      What's Changed

      • Notify the Homebrew tap to bump its formula on each release by @BinSquare in #588
      • cuda: pipelined async forwarding, CUDA graph capture/replay, and cross-connection ordering โ€” vLLM runs end-to-end with CUDA graphs by @BinSquare in #589
      • Official Arch Linux pacman repository by @BinSquare in #591
      • fix(boot): don't stall 5s when agent-rootfs is read-only by @BinSquare in #592
      • Display init commands progress by @mart-e in #573
      • cuda: cuBLASLt descriptor fast path โ€” eager decode 2x on loopback, in-VM eager beats native by @BinSquare in #593
      • Preserve sparseness when copying disk templates during extraction by @BinSquare in #599
      • release: smolvm 1.5.1 by @BinSquare in #594

      New Contributors

      Full Changelog : v1.5.0...v1.5.1

    9. ๐Ÿ”— Kevin Lynagh KiCad helpers and caliper improvements rss

      Hi friends,

      This newsletter we're streamlining the process of creating circuit boards and revisiting the DIY electronic calipers project from two years ago.

      Kevin's KiCad Helpers

      I first started playing around with electronics via Arduino back in 2010, and I designed my first circuit board back in 2015 for my walnut and leather cell phone. Since then, I've made around a dozen PCBs, with a notable spike during the pandemic making weird mechanical keyboards.

      Averaging roughly one PCB/year is a maximally frustrating frequency: I have 100's of hours of cumulative experience, but it's sufficiently spread across the forgetting curve that every time I start something the details are only vaguely familiar and I've got to re-orient myself again. It reminds me of filling out my taxes, where I also furiously consult my notes and attempt to interpret them against new versions of the UI where input boxes have been hidden across other forms and new sub-menus.

      Anyway, armed with a coding agent, I externalized everything I kept forgetting how to do in a pile of scripts: Kevin's KiCad Helpers.

      This is tailored to my needs -- designing PCBs in KiCad 10 to be manufactured by JLCPCB -- but if yours are similar you might find 'em helpful. Even if you don't make PCBs, it might be good inspiration for how to apply LLMs to sand down rough edges of whatever convoluted infrastructure is required for your projects.

      A quick tour of the tools:

      DXF import

      I specify all of my mechanical stuff -- board outlines, mounting hole positions, etc. -- in Autodesk Inventor since it has a constraint solver and allows me to directly reference complex geometry driven by other objects.

      KiCad's GUI has a DXF import tool, but it doesn't have a mechanism to replace already imported geometry, which makes it tedious to iterate.

      This script:

      • imports geometry from a DXF file into a board layer specified by the filename (panel_Edge.Cuts.dxf imports a group ID'd "panel" to the Edge Cuts layer)
      • deletes any geometry previously imported with that ID
      • polls the file for changes, so you get "live reload"

      I combine this with a similar poll + export-to-file script in my mechanical CAD tool (shown above) to get live syncing to KiCad (shown below):

      (Direct video link)

      DXFs map into KiCad's origin at top-left of the page outline and the DXF positive Y direction points up, so most of my PCBs end up drawn outside of the page ยฏ\_(ใƒ„)_/ยฏ.

      STEP export

      I also need the reverse direction: Import a 3D model of a circuit board and its components back into my mechanical CAD program so I can verify clearances, etc. KiCad has native STEP export, but unfortunately some of the parts have extremely detailed 3D models. My M1 Macbook Air running Windows CAD software in a virtual machine does not do well when every pin of every chip is a separate solid body.

      This script generates a STEP export where all parts have been replaced with axis aligned bounding boxes:

      Parts database

      I design all of my boards to be assembled by JLCPCB, so it's extremely helpful to have a low-latency way to query their parts. This script downloads CDFER's daily JLCPCB parts sqlite database and consolidates everything into a single table with a numeric price column and lots of indexes so that searching is fast:

      (Direct video link)

      I use DB Browser for SQLite but you can of course use whatever interface you like.

      It's also extremely useful to point LLM agents at this database:

      My dude, I need an H-bridge that can drive +/- 30 Volts, please query ~/foo/bar/parts.db and give me a table with 5 options for integrated ones showing price / stock / description. Please also make a table showing options for drivers with external transistors. Include links to the datasheets.

      JLCPCB (EasyEDA) schematic and footprint import

      Shout out again to CDFER for their JLCPCB KiCad Library, which has all of the "basic" jellybean parts and some of the extended ones as well.

      For the parts that are not already available in here, I need to import them. Rather than draw them entirely from scratch, I use uPesy's easyeda2kicad.py.

      However I don't always want to create an entirely new symbol and footprint if the part actually corresponds to something that's in the KiCad standard library. So my import tool tries to match existing footprints (including 90 degree rotations) and spawns a terminal UI so you can interactively compare potential matches with the EasyEDA footprint:

      (Direct video link)

      Schematic analysis

      The most interesting helper I've created so far is a general schematic analysis framework , which imports the KiCad netlist and schematic instance properties into a DataScript graph database to run various queries/checks.

      For example, this lil' function calculates the capacitance (of all the explicit capacitors, anyway) on a given net:

      (defn net-capacitance
        [db net-name]
        (some->> (d/q '{:find  [?ref ?v]
                        :in    [$ ?net]
                        :where [[?n :net/name ?net]
                                [?n :net/nodes ?node]
                                [?node :node/pin ?pin]
                                [?i :instance/pins ?pin]
                                [?i :instance/ref ?ref]
                                [(clojure.string/starts-with? ?ref "C")]
                                [?i :instance/value ?v]]}
                      db net-name)
                 (keep (comp parse-capacitance second))
                 seq
                 (reduce + 0.0)))
      

      This function can then be used to check the total capacitance on, e.g., the power nets, and throw an error if it exceeds, e.g., the maximum 10uF allowed by the USB specification.

      (defn check-total-capacitance!
        [db]
        (let [rows (->> ["VCC" "VBUS"] ;;TODO: make this configurable
                        (keep (fn [net]
                                (when-let [c (net-capacitance db net)]
                                  {:net net :total-uF (format "%.2f" (* c 1e6))}))))]
      
          (when (seq rows)
            (print "total capacitance:")
            (clojure.pprint/print-table rows)
      
            (doseq [{:keys [net total-uF]} rows]
              (assert (< (Double/parseDouble total-uF) 10) (str "Net " net " exceeds USB spec 10uF capacitance"))))))
      

      (It's easy to accidentally exceed this limit if you keep incrementally adding ICs and their recommended bypass capacitors.)

      Since KiCad allows one to add arbitrary key/value pairs to schematic instances, it's easy to check and print other data as well. For example, I record the i2c address(es) of each chip this way (note i2c and max_mA fields in property inspector on left):

      Within the schematic text labels I reference using the KiCad text variable format. (E.g., the above Addr: 0x49 label is defined as Addr: ${U1:i2c}.)

      The analysis script throws an error if an address maps to multiple chips:

      (defn i2c-addresses
        [db]
        (d/q '{:find [?hex-addr (distinct ?ref)]
               :where [[?instance :instance/ref ?ref]
                       [?instance :instance/attributes ?attribute]
                       [?attribute :attribute/name "i2c"]
                       [?attribute :attribute/value ?addrs]
                       [(clojure.core/identity ?addrs) [?addr ...]]
                       [(clojure.core/format "0x%x" ?addr) ?hex-addr]]}
             db))
      
      
      (defn check-i2c!
        [db]
        (let [refs-by-addr (i2c-addresses db)]
          (when (seq refs-by-addr)
            (print "i2c addresses")
            (clojure.pprint/print-table (sort-by :addr (for [[addr refs] refs-by-addr]
                                                         {:addr addr :refs (clojure.string/join " "  (sort refs))})))
      
            (doseq [[addr refs] refs-by-addr
                    :when (< 1 (count refs))]
              (throw (ex-info (str "Addr " addr " matches multiple refs: " refs)
                              {:addr addr :refs refs})))
      
            (println ""))))
      

      This also prints out a helpful table of everything on the bus every time I build the project:

      | :addr | :refs |
      |-------+-------|
      |  0x20 |    U3 |
      |  0x49 |    U1 |
      |  0x60 |    U2 |
      |  0x61 |    U2 |
      |  0x62 |    U2 |
      |  0x63 |    U2 |
      |  0x64 |    U2 |
      |  0x65 |    U2 |
      |  0x66 |    U2 |
      |  0x67 |    U2 |
      |  0x68 |    U2 |
      |  0x69 |    U2 |
      |  0x6a |    U2 |
      |  0x6b |    U2 |
      |  0x6c |    U2 |
      |  0x6d |    U2 |
      |  0x6e |    U2 |
      

      (In this example, U2 is an LED driver and exposes each channel on its own i2c address, so its i2c property is specified as 0x60..0x6F.)

      Check/build scripts

      Speaking of builds, most of the functionality described above is packaged up as a script, so you just run kkh build in a folder and it'll create an output directory next to every *.kicad_pro it finds in any subfolder. The outputs are named with the date, git revision, and also indicate whether there are unstaged changes in the repository working tree:

      2026-07-07-73c5c1
      โ”œโ”€โ”€ bom.csv
      โ”œโ”€โ”€ designators.csv
      โ”œโ”€โ”€ netlist.ipc
      โ”œโ”€โ”€ positions.csv
      โ”œโ”€โ”€ receiver-gerbers-2026-07-07-73c5c1.zip
      โ”œโ”€โ”€ receiver.full.step
      โ”œโ”€โ”€ receiver.simplified.step
      โ””โ”€โ”€ schematics
          โ”œโ”€โ”€ receiver-pcb-back.pdf
          โ”œโ”€โ”€ receiver-pcb-front.pdf
          โ””โ”€โ”€ receiver-sch.pdf
      

      So one command runs DRC, ERC, and custom analysis checks and then builds all of the output files required to place a JLCPCB assembly order.

      The build script also exposes the version string as a KiCad variable, so if you add ${KKH_VERSION_DATE} to your PCB silkscreen, the actual version will appear in the output Gerber files.

      I hope by making a proper build script I will never again relive the shame of forgetting to run ERC and having a PCB manufactured where I literally forgot to connect some IC pins entirelyโ€ฆ

      Caliper updates

      About two years ago I had my first foray into digital signal processing and made some electronic calipers.

      I haven't touched the project since I published that post, but a few folks recently asked me about it, and since I had some spare LLM credits I figured I'd spend five minutes sending off the little dude at the problem.

      And I literally mean five minutes -- these are very rough, rambly dictated prompts that I didn't even bother to edit. I'm sharing here to remind everyone (especially myself!) that not everything needs to be A Big New Project and sometimes you can have great success from a quick, low-effort attempt.

      I spawned a Claude session in the project repo, dictated the following prompt, and went to brush my teeth:

      This is a project that I worked on a while ago and the accuracy that came out was alright but I'm wondering if there's anything I can do to improve the accuracy purely from a computational way rather than making new hardware. Be sure to read the blog post mentioned at the top of the read me to get a background

      After I brushed my teeth, it had come up with a few plausible sounding ideas, so I replied with the following and went to sleep:

      I'd like you to set this up and come up with a few different ideas. These are pretty good ones and what I want you to do is Wire them up so that I can test them out Individually as a series of experiments with a given protocol and I want you to set everything up as much as possible So I can do it Quickly on my end without having to get in and change the code or anything like that so what you can do is you can make a branch and then Have maybe different entry points or something for each of these different improvements and then Write like an overall program or something like that Which I can just run to test through it and then make it interactive I guess so that I can You know start it up in the hardware Let it sit still for a certain amount of time Move it a fixed amount and back and then You know or some protocol like that and then I'll tell you what I'm done and then we can just run through that program To test out all of the different ideas and potentially combinations of ideas in like a 10 or 15 minute setting and then We'll figure out from that which ones are working most effectively

      Again, I'm really not trying that hard here.

      I woke up the next morning and spent about 20 minutes setting up the caliper PCBs and then running the programs it generated to collect new measurement data. It tried the following improvement hypotheses (LLM-generated text):

      knob | idea it tests
      ---|---
      window size | longer coherent integration (noise โˆ 1/โˆšN), incl. one 50 Hz line period
      mean_sub | remove DC so ADC offset drift can't leak into the phase
      hann | suppress spectral leakage from non-integer-cycle windows
      smoothing | averaging in I/Q (phasor) space instead of phase space; EMA vs block
      hysteresis | the current 0.1 rad dead-band vs smaller vs none

      and based on the initial results and a bit more chatting, the agent proposed an improvement that is, of course, completely obvious in hindsight. My initial parameter sweep (two years ago) used a fixed 2kHz spacing. However, the actual signal and sampling timings generated by the microcontroller are driven by integer divisions of a fixed clock frequency -- so most of these sampled timings don't "line up" nicely in terms of an integer number of signal periods.

      Thus, there's always a bit of DC bias in the signal, which causes undesired noise.

      With this change, the LLM-generated experimental code reports a noise floor of around 50um, which is about 10x better than what I was getting before. I'm currently on holiday away from my lab, but I expect it'll take 30 minutes once I get back to code it up myself and validate.

      Anyway, the main takeaways for me are:

      1. Write up your work in blog posts so that both people and LLMs can quickly get context and help you out
      2. There's still plenty of low-hanging fruit out there to pick, and with LLMs it's extremely cheap to just ask

      Misc. stuff

  4. July 11, 2026
    1. ๐Ÿ”— IDA Plugin Updates IDA Plugin Updates on 2026-07-11 rss

      IDA Plugin Updates on 2026-07-11

      Activity:

      • disrobe
        • 793b20a2: refresh the pinned python metric to 95.64 percent (6012 of 6286) afteโ€ฆ
        • 30bc29cb: note smartassembly embedded-assembly recovery in the dotnet docs
        • fcaa1d72: recover smartassembly compressed assembly resources
        • b6ac40cd: py-decompile: recover a bare except-continue inside a for-loop whose โ€ฆ
        • e6b293fd: note obfuscar hidden-string recovery in the gauntlet manifest and dotโ€ฆ
      • ida-slides
        • 04c18dc1: Add a screenshot of the plugin in use to the READMEs
        • 63d9982b: Add the macOS standalone-test section to the Korean README
        • a2c1abee: Document the loader DLL arch (x64-only) and update path in the READMEs
        • 0247bc03: Close out the remaining verification-notes items (8, 9, smoke gaps)
        • 30175e64: Fix the pnpm/yarn shim-orphan and node-discovery findings (notes 3, 4)
        • e470d5b0: Apply the 2nd-review cleanup batch: dead code, doc drift, harness covโ€ฆ
        • 20fc9f27: Reconcile the mac static audit against the Windows-side review fixes
        • d0002a8e: Fix the confirmed review findings in the WebView2 renderer
        • e72ff31b: Record the Windows-stack static audit and its live-test checklist
        • df3b150c: Close the macOS-standalone-test item: the harness now exists
        • 95f79097: Add a standalone macOS renderer harness, mirroring the Windows one
        • 12d42bd9: Record remaining verification items in CLAUDE.md
        • 6d74821c: Add Windows support: native WebView2 renderer
        • b53e3c77: Guard the lint against oversized hex tokens and BOM decks
        • 1b044df4: Make the marp render wait stale-proof and settle failures cleanly
        • 5757a528: Consolidate helpers and drop dead code from the review backlog
        • 5e60c54c: Document the slidev save-path engine lock as an accepted behavior
        • 10c46fcd: Harden rare deck-switch and recovery paths
        • cebb063a: Surface marp render errors and harden the load path
        • 7af50563: Ignore stale fileChanged signals for a previously watched path
      • IDAExecFunctionsImporter
        • 5efcc440: AI: improved readme.
        • 2f3d5f04: AI updated readme.
        • e7254856: Added build action for IDA 9.2, 9.3 and 9.4.
        • 0ed4f179: Removed AI test scripts.
        • 3393a933: Removed testfiles and directories.
        • fc3ec2f5: Delete ida_batch_logs directory
        • 1f2aad86: Merge pull request #12 from Fischsalat/IDA-Mappings-V2
      • quokka
        • 71e7180b: Merge pull request #129 from quarkslab/dm/ghidra-xref
    2. ๐Ÿ”— Register Spill Joy & Curiosity #91 rss

      Last week, my 9-year-old started using iMovie on the iPad. She doesn't know anything about editing. She doesn't even know what editing is.

      But we opened iMovie after I told her, "You can use this to cut out the part of the video where you see my hand." She said, "What do you mean, 'cut out'?"

      So we cut the video up, removed some parts, moved others around. She then figured out how to add sounds and a soundtrack. Then, together (me, her, ChatGPT), we learned how to make the soundtrack play only at certain points in the video and so on.

      While we were doing that, I kept thinking that a model or an agent could probably do that. Or maybe in a year. And then you could just say what you want and it would edit the video. Cut here, cut there, make this the first scene, move these around, and so on.

      Then it hit me: she wouldn't know what to say, would she?

      She doesn't know anything about editing. She's seen movies before, sure. That means she's seen J cuts, L cuts, jump cuts, other types of transitions, title screens, and end credits; but she doesn't know what those things are, does she? So how could she ask for them? By pointing at something else? "Make this look like that"? Would that work? Would that lead to the same results?

      And that, of course, made me think about software engineering. I hope it does the same for you.

      • You can now remotely start Amp agents anywhere you can run amp: Agents, Anywhere. We spent a lot of time this week talking to customers and after demoing the things in the Agents Anywhere post on Monday and Tuesday and one person saying "this is the best thing I've seen today, I need this" we decided to get it out as fast as possible. And here we are: agents, anywhere you want.

      • We also launched The Dial which resonates a lot with people. I'm still surprised by what a difference it makes.

      • Mira Murati's Thinking Machines: The Future Worth Building Is Human. There is a lot to love here but I really, really, really, really liked these two paragraphs: "In 2014, Toyota, long a master of the automated plant, brought its expert craftsmen back onto the line with the explicit goal of growing craftsmanship and knowledge. The man who led this, Mitsuru Kawai, put the reason this way:'"To be the master of the machine, you have to have the knowledge and the skills to teach the machine.' The production of knowledge and application of intelligence lift each other; they are not substitutes. The work people do may change, and turn toward more of what only people bring, but the best organizations will make the fullest use of both. AI should enable each organization to be excellent in its own way, not to erase the differences between them." Read the whole thing and then compare it to basically everything Dario Amodei or anyone else from Anthropic has said publicly. When I make that comparison, I'd say that Thinking Machines seems to cherish humanity and Anthropic seems to fetishize Claude and would probably prefer a more human-like Claude over many humans. When I read this line in the article: "Human values, just like human knowledge, reside in the heads of individual people and resist consolidation. But today, the values and voice of AI are decided in a handful of places. A single locus of value alignment, however well run, becomes a locus of power to be captured." I can't help but think of Amodei twirling his hair in his fingers, nodding, saying that AI is going to wipe out however many double digit percent of all entry-level jobs.

      • There it is, finally: Rewriting Bun in Rust. Jarred's post on how he used Fable and "5.9 billion uncached input tokens, 690 million output tokens, and 72 billion cached input token reads -- around $165,000 at API pricing" to rewrite Bun in Rust. It's a very good, very interesting post. There's a lot to think about here, for example: "I think this would've taken 3 engineers with full context on the codebase about a year, during which time we wouldn't be able to improve Node.js compatibility, fix bugs, fix security issues or implement new features. We never would've done that. The realistic alternative was to do nothing and keep fixing the bugs at the top of this post forever." I agree with that. I think it's not something anyone would've done. But the question is: would a company that has to pay API pricing do it now? Read the whole post, this is just one part of it I found interesting, there's more in there.

      • I was this close to not linking to it, because ths newsletter is called Joy & Curiosity after all and not Jesus Christ, Man, Maybe You Shouldn 't Have Posted That? _but you _could argue that it is curious and if one of you hasn't been around for some Ruby or JavaScript drama 2010-2015 and is curious about it then this will give you a taste: Andrew Kelley's Thoughts on the Bun Rust Rewrite.

      • Half-Baked Product. If you've ever been at a startup for more than six months, you will nod to at least some of it. You could argue that the post is very cynical and I'm relatively sure it was even written to be cynical, but I don't know. I find it fascinating. Nearly 15 years of startups and when I read the post I don't how stupid the characters in the story are, but the things they experience are close to unavoidable, question is how to make the best of them.

      • Equally fascinating: How Successful Companies Go Blind. Man, I love reading stuff like this. How companies and organizations grow and change or not change, how incentives change -- I could read about that all day long.

      • If, so far, you made it through life without being neurotic about CO2 levels and air quality, but want to be, go and read this: The bottleneck might be the air in the room. I have only one critique and it's about this part: "And it is invisible from inside. Nobody in the room feels impaired. They feel a little tired, a little foggy, a little checked out, and they put it down to the length of the meeting, a bad night's sleep, or the person who won't stop talking. The one variable almost nobody checks is the air." Clearly the author has never been to Germany.

      • "The Art Institute of Chicago's API includes a has_not_been_viewed_much field on artwork. It's a boolean that describes whether an art piece hasn't been visited on their website very much. [โ€ฆ] what are these artworks? Why aren't they being viewed? I can't answer the latter, but, if you have a moment for the former, please take some time to browse."

      • Maybe you should learn something: "You can learn new things. Pixel art, touch typing, 3d modelling, music, calligraphy, wood working, knitting, a language. Whatever is practical and calls to you, you can learn. In the long term, learning new things is fun and makes life richer in ways you can't even imagine, and it's a time investment that will pay dividends for life as these skills never really go away. There are even social aspects, as you'll quite literally become a more interesting person to talk to." Wonderful.

      • Okay, so we all know John Gruber, author of Daring Fireball, and we also all know that when he writes a post titled Claude's Criminally Bad Electron Mac App Is an Inside Job where it's going: he's going to shit on Electron and say that it's a disgrace to the Mac and that a proper, native macOS application is far better, etc. etc. etc. That's exactly what I expected when reading that post, but -- and excuse the language here -- holy fucking shit , those last three paragraphs? I don't think I've ever seen someone attack a programmer over chosing Electron like this. Hot damn. I mean, Gruber is a great writer and I'm sure that he went over the top like this for comedic effect, but man am I glad to not be Felix Rieseberg.

      • gingerBill: Good Tools Are Invisible. I think I would've vehemently disagreed with him a few years ago on the points re: TUI and Vim. Now? I think I agree with most of it. Good post.

      • "Julius Caesar was the first propagandist. When he was off in Gaul conquering provinces, he would journal and send back snippets to Rome. He wrote so much that in Latin classes today, you study his works. When he came back to Rome, he walked in and they basically handed him the crown. Tell your story, nobody else will do it for you." Good list.

      • Lost and Found: "When something turns up at a stadium or an airport, staff photograph it, log it, and wait. Hundreds of places use one software tool for managing lost items, and I scraped their archives: thousands of accidental portraits of lost stuff."

      • I've been sitting on this next link for a solid year, waiting for just the right moment to share it. I think it's now, but you need to do me a favor: ignore every bias or prejudice you might have and go into this open-minded, okay? Alright. So here it is. It's a one hour video of Tom Platz giving a workshop on barbell squatting in a German gym. You don't have to watch the whole thing, no. But if you've squatted before or seen Tom Platz's legs, you probably want to. What I want to show you are two segments. The first one starts here and goes on for 2-3 minutes. It shows Platz coaching German bodybuilder Hoffmann through a single set of very light squats. It's only 60kg, but the number of reps isโ€ฆ well, insane. If you've never lifted before, yes, I know how ridiculous this looks and sounds. Platz screaming "dig! dig! one more! you're getting stronger!" I mean, it is ridiculous, grown-ass men lifting weights they don't need to lift and shouting at each other while doing so. But watch it! Watch how Hoffmann does "two more!" many, many times ; how he finds another rep somewhere and gets up again; the look he has on his face once he's done a rep and thinks he's done with the set and Platz says "two more!" again; how he then falls over and can't walk; and then how Platz says: "Congratulations, you have achieved failure." And then Hoffmann says "I think I lost the ability to go to 100%. [โ€ฆ] I have all those doubts in my head." The other thing I want you to see is the ending. After squats, they also do leg extensions and then Hoffmann talks to the camera man and again talks about going to 100%: "If you're working out alone you have to constantly remind yourself that you might be training hard, but 95% or 98% isn't enough. Today I was close to 100%. But the real art is to not only do that when there's a camera, but to always do it, even when you're alone, even when it's a Saturday evening and you're alone in the gym and no one's watching. Then you still have to push through. And you have to do it over and over again. Whenever someone asks what the difference is between a normal bodybuilder and a champion: this is it."

      Are you also wondering how much about software you have to know to direct someone else to build something correctly? You should subscribe:

    3. ๐Ÿ”— Jeremy Fielding (YouTube) Engineer Vs Bee - Round 2: Building The Bee Chaser Base. rss

      This work was supported by the Alfred P. Sloan Foundation, enhancing public understanding of science and technology in the modern era, in partnership with IMI: watch what matters. https://www.theimi.co/ & https://sloan.org/programs/public-understanding Check out Mitsuboshi belts ๐Ÿ‘‰http://www.mblusa.com/jeremy-fielding Order custom parts Send Cut Send ๐Ÿ‘‰ http://sendcutsend.com/jeremyfielding If you want to join my community of makers and Tinkers consider getting a YouTube membership ๐Ÿ‘‰ https://www.youtube.com/@JeremyFieldingSr/join

      If you want to chip in a few bucks to support these projects and teaching videos, please visit my Patreon page or Buy Me a Coffee. ๐Ÿ‘‰ https://www.patreon.com/jeremyfieldingsr ๐Ÿ‘‰ https://www.buymeacoffee.com/jeremyfielding

      Social media, websites, and other channel

      Instagram https://www.instagram.com/jeremy_fielding/?hl=en Twitter ๐Ÿ‘‰https://twitter.com/jeremy_fielding TikTok ๐Ÿ‘‰https://www.tiktok.com/@jeremy_fielding0 LinkedIn ๐Ÿ‘‰https://www.linkedin.com/in/jeremy-fielding-749b55250/ My websites ๐Ÿ‘‰ https://www.jeremyfielding.com ๐Ÿ‘‰https://www.fatherhoodengineered.com My other channel Fatherhood engineered channel ๐Ÿ‘‰ https://www.youtube.com/channel/UC_jX1r7deAcCJ_fTtM9x8ZA

      Notes:

      Check out the Formlabs 4L Printer ๐Ÿ‘‰https://bit.ly/4vKl401

      Technical corrections

      Nothing yet

    4. ๐Ÿ”— r/reverseengineering Vidar Stealer 2.0: 1999 Timestamps, Explorer Hollowing & Copilot Injection rss
    5. ๐Ÿ”— Anton Zhiyanov On interactive Go tours rss

      Over the past two years, I've published interactive tours for five Go releases, from 1.22 to 1.26.

      I know some of you have read them, and I've received a lot of kind words from you (even some core Go team members reached out) โ€” thank you so much for that!

      Tour history: Go 1.22 โ€ข 1.23 โ€ข 1.24 โ€ข 1.25 โ€ข 1.26 + Go features by version

      Unfortunately, at some point, writing these tours stopped being fun and started to feel like a part-time job. I'm not really excited about that, so I've decided to stop.

      I still like Go (well, most of it). I read a lot of Go code, I write some Go code, and I write Solod code, which is also Go ๐Ÿ™‚ (Solod is a systems language with Go syntax and a Go-like stdlib).

      I'm still pretty close to the language and will probably continue to write about it.

      But the interactive tours story is over.

    6. ๐Ÿ”— smol-machines/smolvm smolvm v1.5.0 release

      What's Changed

      • Return 404 for a missing registry image and carry raw exec output bytes alongside the lossy text by @BinSquare in #565
      • Brokered P2P layer-blob sharing (engine side) by @BinSquare in #566
      • decompress zstd image layers in the guest agent, not just gzip by @BinSquare in #568
      • nix/smolvm: Update to latest github release by @mmlb in #571
      • Fix the clippy byte_char_slices lint failing CI on main by @BinSquare in #575
      • Bump the libkrunfw submodule to 6.12.95 so overlay re-cuts carry the current guest kernel by @BinSquare in #567
      • fix incomplete ssh-agent examples in the docs by @BinSquare in #576
      • fix ssh-agent forwarding for non-interactive exec/run via the keep-alive container by @BinSquare in #578
      • virtiofs: give user volumes and packed layers a DAX window by @BinSquare in #581
      • fix: reliably kill _boot-vm child on start failure to prevent orphans by @geekgonecrazy in #583
      • libkrun: bump to merged main (b982e75) and refresh all bundled libs by @BinSquare in #584
      • CUDA support: forward-to-host-libs remoting with guest-RAM zero-copy by @BinSquare in #577
      • cuda: auto-stage guest shims into --cuda containers and auto-discover host CUDA libraries by @BinSquare in #585
      • Add Rosetta 2 x86_64 binary translation on Apple Silicon by @BinSquare in #580
      • Rebuild the Windows krun.dll with disk exports and add a bundled-libkrun symbol check to CI by @BinSquare in #586
      • release: smolvm 1.5.0 โ€” guest kernel 6.12.95, zstd image layers, ssh-agent exec/run fix by @BinSquare in #579

      New Contributors

      • @mmlb made their first contribution in #571

      Full Changelog : v1.4.7...v1.5.0

  5. July 10, 2026
    1. ๐Ÿ”— IDA Plugin Updates IDA Plugin Updates on 2026-07-10 rss

      IDA Plugin Updates on 2026-07-10

      New Releases:

      Activity:

      • d810-ng
        • bf3cf089: refactor(plugin): copy ensure_hexrays into manager, drop d810ng import
      • GhidraDec
        • 0cfed4ee: Prepare Ghidra 12.1 and IDA 9.3 release
      • ida-fusion-mcp
        • b58abd6a: fix: restore fresh client installer detection
        • d4173795: fix: close final stabilization review gaps
        • 253e5389: fix: finalize v0.1.1 stabilization candidate
        • 0dc36705: fix: harden IDA runtime boundaries
      • ida_rpc
      • idavim
        • da3a9a7d: Realign section banners with their contents
        • c1569718: Drop the unreachable last_find guard in _repeat_find
        • 35b2f822: Document that ;/, stay with IDA until f/F has been used
        • 0e45eb6b: Let cw rename in the disassembly view too
        • 243284c8: Shorten the manifest description and scope cw to the pseudocode view
        • 972c5591: Trim dead code, skip wasted work, unify the w/e/b loop
        • efe300dc: Fail closed on eventFilter exceptions while a prefix is pending
        • 0504b9f3: Abandon half-typed state on any mouse press
      • mcrit-plugin
        • fcc3f18a: updated README
        • eae80872: Merge pull request #7 from r0ny123/claude/add-matching-params-3uU7s
        • d3862421: Merge branch 'main' into claude/add-matching-params-3uU7s
        • 87241d9a: docs: add docstrings to test helpers, fixtures, and fake classes
        • a346cd98: docs: add docstrings to all SettingsWrapper properties in config.py
        • c59abec5: docs: add comprehensive docstrings to conftest.py and config.py
        • d276d335: Apply suggestions from code review
        • c9ca0f05: Merge pull request #6 from r0ny123/codex/document-hcli-plugin-publishing
      • plugin-ida
        • 9d6d67fd: Merge pull request #160 from RevEngAI/feat-PLU-316
        • 09bbff95: fix: click to edit - prevents code edits
        • 85e76cfa: feat(PLU-316): ai-decomp updates + logging cleanup
        • f4c08192: Merge pull request #159 from RevEngAI/feat-PLU-301
        • ac70f2a2: feat(PLU-301): auto unstrip re-sync
        • 61037188: Merge pull request #158 from RevEngAI/feat-PLU-315
        • 0514fa84: fix: tests
      • rikugan
        • 606cd0ce: chore(release): bump version to 1.10.2
        • 178c2b0b: Merge branch 'fix/exec-python-always-visible'
        • 9b62726a: test(ui): flip execute_python widget tests to always-visible behaviour
        • 38771c8d: refactor(ui): rewrite ExecutePythonWidget to always-visible scrollablโ€ฆ
        • c59aef84: feat(ui): add get_tool_result_editor_style for execute_python output โ€ฆ
        • d702cc0f: docs(plan): execute_python always-visible implementation plan
        • 1d5760f9: docs(spec): execute_python widget always-visible design
        • a0fcd1f5: chore(deps): add pyyaml to dev dependency group
        • b848baa1: fix(test): add pyyaml to requirements for ida_docs_review_prompt tests
        • faade18a: fix(lint): resolve ruff RUF012 and RUF059 in minimax_provider and tesโ€ฆ
        • 42873781: test: restore sys.modules state in theme_manager_signal + fix panel_cโ€ฆ
        • 2c4b7b9e: test: update minimax default model to MiniMax-M3
        • 7bc300b6: chore: bump uv.lock rikugan version to 1.10.1
        • 5746a799: test: fix stub isolation for settings_dialog and a2a_widget
        • cf862072: fix(core): resolve mypy errors in early_log and anthropic_provider
        • f69c0a6b: style(ui): apply ruff format to pre-existing files
    2. ๐Ÿ”— r/reverseengineering Built an eBPF debugger that answers โ€œwho changed what and whenโ€ on Linux rss
    3. ๐Ÿ”— HexRaysSA/plugin-repository commits sync repo: +1 plugin, +2 releases rss
      sync repo: +1 plugin, +2 releases
      
      ## New plugins
      - [mcrit-ida](https://github.com/danielplohmann/mcrit-plugin) (1.1.7)
      
      ## New releases
      - [ida-rpc](https://github.com/bkerler/ida_rpc): 0.1.5
      
    4. ๐Ÿ”— r/reverseengineering I built Magic Extractor โ€” a universal file extraction tool for Windows rss
    5. ๐Ÿ”— Anton Zhiyanov Go-flavored concurrency in C rss

      Go's concurrency is one of the main reasons people like the language. You write go f(), send values through channels, and the runtime scheduler runs thousands of goroutines on just a few OS threads. It feels effortless.

      None of that machinery exists in C. Which made me wonder: how close can you get to Go's concurrency model using only POSIX threads? Obviously, native OS threads can't match the efficiency of lightweight goroutines, but what is the actual cost, when does it become a problem, and is there any way to at least partially avoid it?

      I ran into these questions while adding concurrency to Solod (So), a strict subset of Go that translates to plain C, with no runtime and no garbage collector. In the end, I came to the conclusion that you can do quite a lot with pthreads โ€” as long as you're honest about the tradeoffs.

      This post is about the POSIX threads-based concurrency model I chose, the benefits it offers, and its limitations.

      Mutex/Cond โ€ข Atomics โ€ข Pool โ€ข Channel โ€ข Performance โ€ข Design โ€ข Wrapping up

      Mutex/Cond Everything in So's concurrency stack is built on two basic POSIX primitives: the mutex and the condition variable. sync.Mutex is a thin wrapper around pthread_mutex_t: // Extracted from So's stdlib source code. type Mutex struct { mu pthread_mutex_t } func (m *Mutex) Lock() { rc := pthread_mutex_lock(&m.mu) if rc != 0 { panic("sync: Mutex.Lock failed") } } Since So translates to C, this is basically a struct that holds a pthread_mutex_t and a function that calls pthread_mutex_lock. Here's the transpiler output: // The translated C code. typedef struct sync_Mutex { pthread_mutex_t mu; } sync_Mutex; void sync_Mutex_Lock(sync_Mutex* m) { int rc = pthread_mutex_lock(&m->mu); if (rc != 0) { so_panic("sync: Mutex.Lock failed"); } } That is the whole translation โ€” the generated C is a near-mechanical mirror of the So code, only noisier. From here on, I'll mainly show the So version, but I'll also provide the C code for those who are interested. There's nothing exciting here: sync.Mutex is a pthread mutex wrapper that panics if something goes wrong (which is rare). The companion primitive is sync.Cond, a wrapper around pthread_cond_t. It's the standard "wait until a condition holds" tool, associated with a mutex: type Cond struct // wraps pthread_cond_t + pthread_mutex_t func (c *Cond) Wait() // wraps pthread_cond_wait func (c *Cond) Signal() // wraps pthread_cond_signal func (c *Cond) Broadcast() // wraps pthread_cond_broadcast Show the translated C code typedef struct sync_Cond { pthread_cond_t cond; sync_Mutex* mu; } sync_Cond; void sync_Cond_Wait(sync_Cond* c); // wraps pthread_cond_wait void sync_Cond_Signal(sync_Cond* c); // wraps pthread_cond_signal void sync_Cond_Broadcast(sync_Cond* c); // wraps pthread_cond_broadcast These two types โ€” Mutex and Cond โ€” are the foundation. Other concurrency tools โ€” Once, the thread pool, channels โ€” are built using a mutex and one or more condition variables. This has several effects on performance, as we'll see later. Atomics Not everything needs a lock. So's sync/atomic mirrors Go's: Bool, Int32, Int64, Uint32, Uint64, and a generic Pointer[T], all with Load, Store, Swap, and CompareAndSwap methods. The nice thing is that these don't need pthreads at all. They map directly to the C compiler's __atomic builtins โ€” the same hardware instructions that Go's compiler emits. So there's no reason for them to be any slower, and they're not: Atomic op | Go | So | Winner ---|---|---|--- Load | 2ns | 2ns | ~same Store | 2ns | 2ns | ~same CompareAndSwap | 13ns | 13ns | ~same Each number is the cost of one operation on a single thread. sync.Once is a good example of using atomics effectively. Its fast path only needs a single atomic load โ€” after the given function runs, every future call to Do checks a flag and returns: type Once struct { mu Mutex done atomic.Bool } // Do calls f if and only if Do is being called // for the first time for this o. func (o *Once) Do(f func()) { if o.done.Load() { // lock-free fast path return } // slow path... } Show the translated C code typedef struct sync_Once { sync_Mutex mu; atomic_Bool done; } sync_Once; // Do calls f if and only if Do is being called // for the first time for this o. void sync_Once_Do(sync_Once* o, void (*f)()) { if (atomic_Bool_Load(&o->done)) { // lock-free fast path return; } // slow path... } Worker pool To actually run code concurrently, you need threads. The conc.Thread type wraps pthread_t and its related functions: type Thread struct // wraps pthread_t func (th Thread) Wait() any // wraps pthread_join func (th Thread) Detach() // wraps pthread_detach Show the translated C code typedef struct conc_Thread { pthread_t t; } conc_Thread; void* conc_Thread_Wait(conc_Thread th); // wraps pthread_join void conc_Thread_Detach(conc_Thread th); // wraps pthread_detach Consider this conc.Go function: // Go launches an OS thread that runs fn(arg) and returns a handle to it. func Go(entry func(any) any, arg any) Thread { var th Thread rc := pthread_create(&th.t, nil, entry, arg) // ... } Show the translated C code // Go launches an OS thread that runs fn(arg) and returns a handle to it. // `any` in So translates to `void*` in C. conc_Thread conc_Go(void* (*entry)(void*), void* arg) { conc_Thread th = {0}; int rc = pthread_create(&th.t, NULL, entry, arg); // ... } Usage example: func work(arg any) any { acc := arg.(*Account) // ... } func main() { var acc Account th := conc.Go(work, &acc) // ... do other work concurrently ... th.Wait() // work is complete once Wait returns } Show the translated C code void* work(void* arg) { main_Account* acc = (main_Account*)arg; // ... } int main(void) { main_Account acc = {0}; conc_Thread th = conc_Go(work, &acc); // ... do other work concurrently ... conc_Thread_Wait(th); // work is complete once Wait returns } It might look like go work(&acc), but that's just on the surface. conc.Go starts an actual OS thread, not a goroutine. You have to eventually call Wait to join or Detach it, or else its resources will leak. Also, OS threads are expensive to create โ€” they're nothing like Go's goroutines, which only need a few kilobytes of stack and start up in nanoseconds. That's exactly why you usually don't want to call Go inside a loop. For tasks that are short-lived or happen often, it's better to use a pool of long- lived worker threads and send tasks to them. conc.Pool to the rescue: Worker thread pool in So โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Task 1 โ”‚ โ”‚ Task 2 โ”‚...โ”‚ Task M โ”‚ M tasks โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ conc.Pool โ”‚ coordinator โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Thrd 1 โ”‚ โ”‚ Thrd 2 โ”‚...โ”‚ Thrd N โ”‚ N threads, N << M โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ OS scheduler โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Usage example: type Task struct { in int out int } func square(arg any) { task := arg.(*Task) task.out = task.in * task.in } func main() { tasks := make([]Task, 10) opts := conc.PoolOpts{NumThreads: 2} pool := conc.NewPool(mem.System, opts) defer pool.Free() for i := range tasks { tasks[i].in = i pool.Go(square, &tasks[i]) } pool.Wait() } Show the translated C code typedef struct main_Task { so_int in; so_int out; } main_Task; void square(void* arg) { main_Task* task = (main_Task*)arg; task->out = task->in * task->in; } int main(void) { so_Slice tasks = so_make_slice(main_Task, 10, 10); conc_PoolOpts opts = (conc_PoolOpts){.NumThreads = 2}; conc_Pool* pool = conc_NewPool(mem_System, opts); for (so_int i = 0; i < so_len(tasks); i++) { // so_at is a generic macro to get the i-th element of a // specific type (main_Task here) from a type-erased slice. // Here we're getting the i-th task from the tasks slice. so_at(main_Task, tasks, i).in = i; conc_Pool_Go(pool, square, &so_at(main_Task, tasks, i)); } conc_Pool_Wait(pool); conc_Pool_Free(pool); } The first argument to NewPool, mem.System, is a memory allocator. Solod avoids hidden allocations, so anything that needs memory takes an allocator explicitly โ€” here it backs the pool's task queue. Under the hood, a Pool is a fixed group of worker threads that pull tasks from a shared queue (a ring buffer). It uses one mutex and a few condition variables: // Pool is a bounded pool of worker threads with a wait queue // which execute tasks of the form func(any). type Pool struct { alloc mem.Allocator mu sync.Mutex notEmpty sync.Cond // signaled when a task is enqueued notFull sync.Cond // signaled when a slot frees allDone sync.Cond // broadcast when no task is in flight workers []Thread queue []task // ring buffer of submitted tasks active int // tasks submitted but not yet finished stopped bool // set by Free to drain and exit } // NewPool creates a pool with a given number // of worker threads and starts them. func NewPool(alloc mem.Allocator, opts PoolOpts) *Pool // Go submits a task for execution, blocking while the queue is full. func (p *Pool) Go(fn func(any), arg any) // Wait blocks until all submitted tasks finish. func (p *Pool) Wait() Show the translated C code // Pool is a bounded pool of worker threads with a wait queue // which execute tasks of the form func(any). typedef struct conc_Pool { mem_Allocator alloc; sync_Mutex mu; sync_Cond notEmpty; // signaled when a task is enqueued sync_Cond notFull; // signaled when a slot frees sync_Cond allDone; // broadcast when no task is in flight so_Slice workers; so_Slice queue; // ring buffer of submitted tasks so_int active; // tasks submitted but not yet finished bool stopped; // set by Free to drain and exit } conc_Pool; conc_Pool* conc_NewPool(mem_Allocator alloc, conc_PoolOpts opts); void conc_Pool_Go(conc_Pool* p, void (*fn)(void*), void* arg); void conc_Pool_Wait(conc_Pool* p); notEmpty wakes up a worker when there are tasks to do, notFull applies back-pressure when the queue is full, and allDone lets Wait know when everything is finished. It's a classic producer-consumer setup, about 200 lines of code, and there's nothing fancy about it. The heart of the pool is the worker loop. Each thread blocks until a task appears, runs it outside the lock so workers execute in parallel, then records that it finished: // workerMain runs on every pool thread: pull a task, run it, repeat. func workerMain(arg any) any { p := arg.(*Pool) for { p.mu.Lock() for p.qempty() && !p.stopped { p.notEmpty.Wait() // sleep until a task is enqueued } if p.qempty() && p.stopped { p.mu.Unlock() break // queue drained and pool shutting down } t := p.qpop() p.notFull.Signal() // a slot freed for a waiting submitter p.mu.Unlock() t.fn(t.arg) // run the task with the lock released p.mu.Lock() p.active-- if p.active == 0 { p.allDone.Broadcast() // wake anyone parked in Wait } p.mu.Unlock() } return nil } Show the translated C code // workerMain runs on every pool thread: pull a task, run it, repeat. static void* workerMain(void* arg) { conc_Pool* p = (conc_Pool*)arg; for (;;) { sync_Mutex_Lock(&p->mu); for (; conc_Pool_qempty(p) && !p->stopped;) { sync_Cond_Wait(&p->notEmpty); // sleep until a task is enqueued } if (conc_Pool_qempty(p) && p->stopped) { sync_Mutex_Unlock(&p->mu); break; // queue drained and pool shutting down } task t = conc_Pool_qpop(p); sync_Cond_Signal(&p->notFull); // a slot freed for a waiting submitter sync_Mutex_Unlock(&p->mu); t.fn(t.arg); // run the task with the lock released sync_Mutex_Lock(&p->mu); p->active--; if (p->active == 0) { sync_Cond_Broadcast(&p->allDone); // wake anyone parked in Wait } sync_Mutex_Unlock(&p->mu); } return NULL; } This is what separates a pool from a plain queue. Pool.Go bumps active as it enqueues; each worker decrements it after running a task, and the last one out broadcasts allDone. Pool.Wait sleeps until the count hits zero: // Wait blocks until every submitted task has finished. func (p *Pool) Wait() { p.mu.Lock() for p.active != 0 { p.allDone.Wait() } p.mu.Unlock() } Show the translated C code // Wait blocks until every submitted task has finished. void conc_Pool_Wait(conc_Pool* p) { sync_Mutex_Lock(&p->mu); for (; p->active != 0;) { sync_Cond_Wait(&p->allDone); } sync_Mutex_Unlock(&p->mu); } The tradeoff is that the number of worker threads is fixed. In Go, a program can handle thousands of concurrent I/O waits because blocked goroutines use very little memory. A So pool can't do this โ€” if all N workers are parked on a blocking syscall, the pool is stalled until one returns. You have to set the pool size based on the workload, instead of letting the runtime manage it for you. Channel Channels are an important part of Go's concurrency model, and So's conc.Chan[T] gives you something quite similar. Just like in Go, it passes values by copy and comes in buffered and unbuffered flavors: ch := conc.NewChan // buffered, capacity 2 defer ch.Free() // Producer on its own thread. prod := producer{ch: &ch, n: 5} thr := conc.Go(produce, &prod) defer thr.Wait() // Consume until the channel is closed and drained. var v int for ch.Recv(&v) { fmt.Printf("received %d\n", v) } Show the translated C code // conc_NewChan, conc_Chan_Recv, and friends are generic macros: // the element type (so_int here) is passed as the first argument. conc_Chan ch = conc_NewChan(so_int, mem_System, 2); // buffered, capacity 2 // Producer on its own thread. producer prod = (producer){.ch = &ch, .n = 5}; conc_Thread thr = conc_Go(produce, &prod); // Consume until the channel is closed and drained. so_int v = 0; for (; conc_Chan_Recv(so_int, &ch, &v);) { fmt_Printf("received %d\n", v); } conc_Thread_Wait(thr); conc_Chan_Free(so_int, &ch); Chan[T] is a thin generic shell over one of two engines, picked at creation time: Buffered (n > 0) is a mutex-guarded ring buffer with notEmpty and notFull condition variables โ€” like the Pool queue. Senders block when it's full, receivers block when it's empty. type Buffer struct { alloc mem.Allocator mu sync.Mutex notEmpty sync.Cond // signaled when an item becomes available notFull sync.Cond // signaled when a slot frees buf mem.Array // ring buffer closed bool // true after Close } // Send copies v into the ring, blocking while it is full. func (ch *Buffer) Send(v any) { ch.mu.Lock() for ch.bfull() { ch.notFull.Wait() // back-pressure until a slot frees } ch.bpush(v) ch.notEmpty.Signal() // wake one waiting receiver ch.mu.Unlock() } Show the translated C code typedef struct conc_Buffer { mem_Allocator alloc; sync_Mutex mu; sync_Cond notEmpty; // signaled when an item becomes available sync_Cond notFull; // signaled when a slot frees mem_Array buf; // ring buffer bool closed; // true after Close } conc_Buffer; // Send copies v into the ring, blocking while it is full. void conc_Buffer_Send(conc_Buffer* ch, void* v) { sync_Mutex_Lock(&ch->mu); for (; conc_Buffer_bfull(ch);) { sync_Cond_Wait(&ch->notFull); // back-pressure until a slot frees } conc_Buffer_bpush(ch, v); sync_Cond_Signal(&ch->notEmpty); // wake one waiting receiver sync_Mutex_Unlock(&ch->mu); } The full implementation also checks for closed, but I left it out for brevity. Recv is the mirror method: block while empty, pop the next value, signal notFull to wake a sender. It also handles the closed channel, returning false once the buffer is closed and drained. The rest is this lock-wait- signal core. Buffer source code Unbuffered (n == 0) is a rendezvous: each send blocks until a receiver takes the value, copying vsize bytes directly from the sender's stack to the receiver's destination without using an intermediate buffer. type Rendezvous struct { alloc mem.Allocator vsize int // size in bytes of a handed-off value mu sync.Mutex cond sync.Cond // broadcast on every slot state change src any // the sender's published value (valid while full) full bool // a value is published and not yet freed claimed bool // the published value has been taken by a receiver closed bool // true after Close } // Send publishes v and waits for a receiver to take it. func (ch *Rendezvous) Send(v any) { ch.mu.Lock() for ch.full { ch.cond.Wait() // wait for the previous hand-off to finish } ch.src, ch.full, ch.claimed = v, true, false // publish ch.cond.Broadcast() // wakeup #1: wake a receiver for !ch.claimed { ch.cond.Wait() // wait until the value is taken } ch.src, ch.full = nil, false // free the slot ch.cond.Broadcast() ch.mu.Unlock() } Show the translated C code typedef struct conc_Rendezvous { mem_Allocator alloc; so_int vsize; // size in bytes of a handed-off value sync_Mutex mu; sync_Cond cond; // broadcast on every slot state change void* src; // the sender's published value (valid while full) bool full; // a value is published and not yet freed bool claimed; // the published value has been taken by a receiver bool closed; // true after Close } conc_Rendezvous; // Send publishes v and waits for a receiver to take it. void conc_Rendezvous_Send(conc_Rendezvous* ch, void* v) { sync_Mutex_Lock(&ch->mu); for (; ch->full;) { sync_Cond_Wait(&ch->cond); // wait for the previous hand-off to finish } ch->src = v; // publish ch->full = true; ch->claimed = false; sync_Cond_Broadcast(&ch->cond); // wakeup #1: wake a receiver for (; !ch->claimed;) { sync_Cond_Wait(&ch->cond); // wait until the value is taken } ch->full = false; // free the slot ch->src = NULL; sync_Cond_Broadcast(&ch->cond); sync_Mutex_Unlock(&ch->mu); } Recv is the other half: it waits for a published, unclaimed value, copies vsize bytes straight from the sender's stack into dst (no intermediate buffer), marks it as claimed, and broadcasts to wake the sender back, creating wakeup #2. One hand-off, two wakeups. Copying directly from the sender's stack is safe because of that second wakeup. src is a pointer to v, which lives on the sender's stack. While the receiver is reading it, the sender is parked in for !ch.claimed { ch.cond.Wait() }, so its stack frame stays alive. The sender only returns (and reclaims that memory) after the receiver sets claimed and wakes it up. There's no need to copy into a shared buffer because the source is guaranteed to outlive the read. Rendezvous source code As you can see, the API is pretty similar to Go. Now let's look at the numbers. Performance

      Here's the main tradeoff: pthread-based concurrency primitives are fast when no one has to block, but they get slow when someone does. And it's always for the same reason.

      Go schedules goroutines in userspace. When one goroutine blocks on a channel and another wakes it up, the runtime moves them between its own queues โ€” no kernel involved. POSIX threads, on the other hand, don't provide a userland scheduler. When a thread blocks on a condition variable, it parks in the kernel, and waking it up requires a syscall. Every hand-off between threads that actually parks pays the cost of a syscall on both ends.

      You can clearly see the difference in the mutex benchmarks. With 8 competing threads, it all comes down to whether the waiting threads have to park or not:

      Mutex benchmark | Go | So | Winner
      ---|---|---|---
      Uncontended, 1 thread | 14ns | 9ns | So - 1.6x
      Contended spin, 8 threads | 75ns | 27ns | So - 2.8x
      Contended work, 8 threads | 1.1ยตs | 2.0ยตs | Go - 1.8x

      Each number is the average time for a single Lock/Unlock pair. The uncontended benchmark runs on one thread, while the contended benchmarks have multiple threads fighting over the same mutex.

      Notice that So actually wins the first two benchmarks, and for good reason. So's Lock is a plain pthread_mutex_lock call with nothing extra, while Go's sync.Mutex adds more overhead โ€” like starvation-mode tracking and a runtime that stays involved because a goroutine can be preempted in the middle of a critical section.

      When nobody parks, that overhead is the main cost, and the thinner wrapper is closer to the hardware. With an empty critical section (the spin benchmark), a waiting thread grabs the lock while still spinning and almost never parks โ€” So wins by 2.8x. The uncontended benchmark (a single thread, no contention) shows the same thing: less code between the call and the lock, so 9ns versus 14ns.

      The picture flips the moment threads have to park. Give the critical section about a microsecond of real work (the work benchmark) and waiters exhaust their spin budget and park. Now every hand-off costs a wakeup syscall, and So drops to half of Go's throughput. The work is identical in both cases โ€” the difference comes from the parking cost.

      Condition variables demonstrate this clearly because they always park:

      Cond benchmark | Go | So | Winner
      ---|---|---|---
      1 waiter | 150ns | 1.5ยตs | Go - 10x
      8 waiters | 2.0ยตs | 14ยตs | Go - 7.0x
      32 waiters | 9.0ยตs | 60ยตs | Go - 6.7x

      Each number is the cost of one rendezvous round: a single broadcast that wakes every waiter and hands control back, with N waiters plus one broadcaster.

      Pthread-based condition variable is consistently 7-10 times slower. There's no trick to close this gap โ€” it's just the cost of waking up a real OS thread instead of a goroutine.

      Channels have the same issue because they're built using mutexes and condition variables:

      Chan benchmark | Go | So | Winner
      ---|---|---|---
      Uncontended, 1 thread | 24ns | 21ns | So - 1.1x
      Unbuffered, 2 threads | 130ns | 3.0ยตs | Go - 23x
      Buffered (10), 2 threads | 44ns | 400ns | Go - 9.1x
      Buffered (100), 2 threads | 33ns | 70ns | Go - 2.1x

      Each number is the cost of moving one value through the channel (send plus its matching receive). The number in parentheses is the buffer capacity.

      The uncontended case fills and drains a buffer from a single thread, so nothing ever blocks โ€” it's just a lock plus a copy, which gives So a slight advantage. But the moment a producer and consumer actually start handing off work, So has to wake up a thread for every transfer that gets parked. It's worst for the unbuffered channel, where every value is a rendezvous with two wakeups: 23x slower. A larger buffer helps a lot โ€” with room for 100 items, most sends go through without waking anyone, and the gap narrows to about 2x.

      The consequence is that the larger your tasks are, the better pthread-based concurrency works. If you use a channel for fine-grained, value-at-a-time streaming between threads, performance will suffer. But if you use a channel to pass whole work items to a pool, where each item takes tens of microseconds to process, the wakeup cost becomes negligible. The pool benchmarks on realistic workloads confirms this:

      Pool benchmark | Go | So | Winner
      ---|---|---|---
      1000 CPU tasks (~40ยตs each) | 7ms | 8ms | Go - 1.1x
      64 IO tasks (1ms block each) | 9ms | 10ms | Go - 1.1x

      Each number is the wall-clock time for 8 workers to process the whole batch.

      Here, So is within 1.1x of Go. The per-task dispatch cost is still present, but it's spread out over real work, and the performance penalty is pretty small.

      Benchmarking

      All benchmarks were run on an Apple M1 CPU running macOS. The C code was compiled with Clang 16 using these CFLAGS and mimalloc as the system allocator:

      -Ofast -march=native -flto -funroll-loops -DNDEBUG
      

      The results shown are the medians from several benchmark runs. Each benchmark ran many iterations, following the same logic as Go's own benchmarking.

      The Go benchmarks used Go 1.26 and go test -bench=..

      Source code for both So's and Go's benchmarks: conc โ€ข sync

      Here's a summary of the strengths and weaknesses of the pthread-based approach:

      • โž• Coarse-grained pooled workloads are within about 10% of Go's performance.
      • โž• Uncontended locks and spin-friendly critical sections perform quite well.
      • โž• Atomic operations are as fast as in Go.
      • โž• The implementation is 100x simpler.
      • โž– Anything that needs to park and wake an OS thread is much slower than Go's userspace scheduler.
      • โž– The pool can't handle thousands of blocked waiters like goroutines can.

      If you're looking for "thousands of cheap goroutines", the pthread-based approach will let you down. But if you're fine with "a few worker threads handling lots of tasks", it holds up well.

      Design decisions

      Three decisions influenced the way I implemented concurrency in Solod.

      Pthreads, not fibers. I know there are coroutine/fiber libraries for C that avoid the kernel wakeup cost โ€” single-threaded ones like neco, and multi-threaded ones like libfiber. A userspace scheduler is exactly what would help to match Go in the benchmarks above.

      I decided not to use one. I wanted something dead simple โ€” an approach I could explain in a paragraph, using tools every C programmer already knows. The trade-off is that you lose some performance with fine-grained blocking, but in many real-world situations, pthreads work fine if you use a worker pool. For me, keeping things simple is more important than saving a few microseconds during task hand-offs. For now, at least.

      Standard library, not language. Go bakes goroutines, channels, and select right into the language. I decided to keep everything in the stdlib for two reasons.

      โž€ It follows So's "no hidden allocations" rule. In Go, go f() quietly allocates a goroutine stack, and make(chan T, n) allocates a buffer. In So, all allocations are explicit: you pass an allocator to NewChan and NewPool, and you always know exactly where the memory comes from โ€” whether it's the system allocator, an arena, or something else.

      โž A library is more flexible. Since a pool is a regular value, you can have as many as you need, each sized for its specific purpose. In a multi-stage pipeline where each stage needs a different capacity, you can start one pool per stage, each with its own NumThreads and QueueSize, instead of being given a single global scheduler. The language stays simple, and the flexibility is in code you can easily read.

      Timeouts, not select. Go's select waits on several channel operations at once and proceeds with whichever is ready first. Implementing it would require a lot of work โ€” a thread has to register interest on multiple channels, block once, and then wake up when any of them is ready โ€” so I left it out. Instead, Chan offers SendTimeout and RecvTimeout, which cover two common uses of select with a single channel:

      • "Do this, but give up after a while" (Go's case <-time.After(...) idiom).
      • "Do this only if it won't block" (Go's non-blocking default branch).

      What's missing is the ability to block on multiple channels at once and continue with whichever one is ready first, as well as the option to mix sends and receives in the same selection.

      Wrapping up

      How close can you get to Go's concurrency using only pthreads? Close enough to be useful, but not enough to really match Go. You can wrap real OS threads with familiar APIs โ€” mutexes, condition variables, pools, channels โ€” and the code will look and act a lot like Go, at least until a thread needs to block. But there's no scheduler underneath, so when a thread blocks, it's an actual thread waiting in the kernel, not a goroutine that's paused for free. That's the main limitation of this approach.

      What you get in return is brutal simplicity. Every primitive is a thin wrapper with no runtime hiding behind it, so the performance is exactly what the OS gives you: fast atomics, fast uncontended locks, and pooled throughput within ~10% of Go on coarse-grained work. But as soon as you switch to fine-grained, one-value-at-a-time hand-offs, the cost of kernel wakeups becomes the main factor, and you'll notice the slowdown.

      If you think the pthread approach might work for you, I invite you to try Solod. It includes the sync and conc packages, along with many others ported from Go's standard library.

    6. ๐Ÿ”— r/reverseengineering I reverse-engineered the DJI Spark smart battery I2C/SMBus protocol and documented the captures, firmware, and hardware rss
    7. ๐Ÿ”— r/reverseengineering Old game (Windows, Ka'Roo 2000) rewrite + reverse engineered rss
    8. ๐Ÿ”— r/reverseengineering Detecting Hardware Trojans in High-Level Synthesis-Generated RTL using Large Language Models rss
    9. ๐Ÿ”— benji.dog rss

      O: "Burj Khalifa is the tallest building, right?" Me: "yes" O: "So what's the shortest building?" Me: ๐Ÿ˜ถ

    10. ๐Ÿ”— r/reverseengineering Reverse-engineering a Neopets shop with 48,000 inventory snapshots rss
    11. ๐Ÿ”— New Music Releases Bring Me the Horizon - Count Your Blessings | Repented rss

      Bring Me the Horizon - a new release is available:

      • 2026-07-10: Count Your Blessings | Repented (Album)

      Amazon: Canada | Deutschland | France | United Kingdom | United States

      Visit muspy for more information.

    12. ๐Ÿ”— New Music Releases If These Trees Could Talk - The Hidden Hand rss

      If These Trees Could Talk - a new release is available:

      • 2026-07-10: The Hidden Hand (Album)

      Amazon: Canada | Deutschland | France | United Kingdom | United States

      Visit muspy for more information.

    13. ๐Ÿ”— New Music Releases Kygo - Take Me Back rss

      Kygo - a new release is available:

      • 2026-07-10: Take Me Back (Single)

      Amazon: Canada | Deutschland | France | United Kingdom | United States

      Visit muspy for more information.