First commit
diff --git a/emsdk.py b/emsdk.py
index 8a314df..99f063f 100644
--- a/emsdk.py
+++ b/emsdk.py
@@ -143,11 +143,7 @@
 elif machine.endswith('86'):
   ARCH = 'x86'
 elif machine.startswith('aarch64') or machine.lower().startswith('arm64'):
-  if WINDOWS:
-    errlog('No support for Windows on Arm, fallback to x64')
-    ARCH = 'x86_64'
-  else:
-    ARCH = 'arm64'
+  ARCH = 'arm64'
 elif machine.startswith('arm'):
   ARCH = 'arm'
 else:
@@ -2714,7 +2710,7 @@
     errlog("Missing command; Type 'emsdk help' to get a list of commands.")
     return 1
 
-  debug_print('esmdk.py running under `%s`' % sys.executable)
+  debug_print('emsdk.py running under `%s`' % sys.executable)
   cmd = args.pop(0)
 
   if cmd in ('help', '--help', '-h'):
diff --git a/emsdk_manifest.json b/emsdk_manifest.json
index 61b3e2d..63a85bb 100644
--- a/emsdk_manifest.json
+++ b/emsdk_manifest.json
@@ -396,6 +396,19 @@
 
 
   {
+    "id": "node",
+    "version": "23.0.0",
+    "arch": "arm64",
+    "bitness": 64,
+    "windows_url": "node-v23.0.0-win-arm64.zip",
+    "activated_path": "%installation_dir%/bin",
+    "activated_path_skip": "node",
+    "activated_cfg": "NODE_JS='%installation_dir%/bin/node%.exe%'",
+    "activated_env": "EMSDK_NODE=%installation_dir%/bin/node%.exe%"
+  },
+
+
+  {
     "id": "python",
     "version": "3.9.2-nuget",
     "bitness": 64,
@@ -431,6 +444,17 @@
     "activated_cfg": "PYTHON='%installation_dir%/bin/python3'",
     "activated_env": "EMSDK_PYTHON=%installation_dir%/bin/python3;SSL_CERT_FILE=%installation_dir%/lib/python3.9/site-packages/certifi/cacert.pem"
   },
+
+  {
+    "id": "python",
+    "version": "3.13.0",
+    "bitness": 64,
+    "arch": "arm64",
+    "windows_url": "python-3.13.0-0-arm64.zip",
+    "activated_cfg": "PYTHON='%installation_dir%/python.exe'",
+    "activated_env": "EMSDK_PYTHON=%installation_dir%/python.exe"
+  },
+
   {
     "id": "java",
     "version": "8.152",
@@ -622,6 +646,14 @@
   {
     "version": "main",
     "bitness": 64,
+    "arch": "arm64",
+    "uses": ["python-3.13.0-64bit", "llvm-git-main-64bit", "node-23.0.0-64bit", "emscripten-main-64bit", "binaryen-main-64bit"],
+    "os": "win"
+  },
+  {
+    "version": "main",
+    "bitness": 64,
+    "arch": "amd64",
     "uses": ["python-3.9.2-nuget-64bit", "llvm-git-main-64bit", "node-18.20.3-64bit", "emscripten-main-64bit", "binaryen-main-64bit"],
     "os": "win"
   },
diff --git a/scripts/update_python.py b/scripts/update_python.py
index a5848ea..432bf0b 100755
--- a/scripts/update_python.py
+++ b/scripts/update_python.py
@@ -10,10 +10,10 @@
 We only supply binaries for windows and macOS, but we do it very different ways for those two OSes.
 
 Windows recipe:
-  1. Download the "embeddable zip file" version of python from python.org
-  2. Remove .pth file to work around https://bugs.python.org/issue34841
-  3. Download and install pywin32 in the `site-packages` directory
-  4. Re-zip and upload to storage.google.com
+  1. Download precompiled version of python from NuGet package manager,
+     either the package "python" for AMD64, or "pythonarm64" for ARM64.
+  2. Set up pip and install pywin32 and psutil via pip for emrun to work.
+  3. Re-zip and upload to storage.google.com
 
 macOS recipe:
   1. Clone cpython
@@ -31,13 +31,13 @@
 import sys
 from subprocess import check_call
 
-version = '3.9.2'
+version = '3.13.0'
 major_minor_version = '.'.join(version.split('.')[:2])  # e.g. '3.9.2' -> '3.9'
-download_url = 'https://www.nuget.org/api/v2/package/python/%s' % version
-revision = '4'
+# This is not part of official Python version, but a repackaging number appended by emsdk
+# when a version of Python needs to be redownloaded.
+revision = '0'
 
-pywin32_version = '227'
-pywin32_base = 'https://github.com/mhammond/pywin32/releases/download/b%s/' % pywin32_version
+PSUTIL = 'psutil==6.0.0'
 
 upload_base = 'gs://webassembly/emscripten-releases-builds/deps/'
 
@@ -60,36 +60,45 @@
     return ['zip', '-rq']
 
 
+# Detects whether current python interpreter architecture is ARM64 or AMD64
+# If running AMD64 python on an ARM64 Windows, this still intentionally returns AMD64
+def find_python_arch():
+    import sysconfig
+    arch = sysconfig.get_platform().lower()
+    if 'amd64' in arch:
+        return 'amd64'
+    if 'arm64' in arch:
+        return 'arm64'
+    raise f'Unknown Python sysconfig platform "{arch}" (neither AMD64 or ARM64)'
+
+
 def make_python_patch():
-    pywin32_filename = 'pywin32-%s.win-amd64-py%s.exe' % (pywin32_version, major_minor_version)
-    filename = 'python-%s-amd64.zip' % (version)
-    out_filename = 'python-%s-%s-amd64+pywin32.zip' % (version, revision)
-    if not os.path.exists(pywin32_filename):
-        url = pywin32_base + pywin32_filename
-        print('Downloading pywin32: ' + url)
-        urllib.request.urlretrieve(url, pywin32_filename)
+    python_arch = find_python_arch()
+    package_name = 'pythonarm64' if python_arch == 'arm64' else 'python'
+    download_url = f'https://www.nuget.org/api/v2/package/{package_name}/{version}'
+    filename = f'python-{version}-{python_arch}.zip'
+    out_filename = f'python-{version}-{revision}-{python_arch}.zip'
 
     if not os.path.exists(filename):
-        print('Downloading python: ' + download_url)
+        print(f'Downloading python: {download_url} to {filename}')
         urllib.request.urlretrieve(download_url, filename)
 
     os.mkdir('python-nuget')
     check_call(unzip_cmd() + [os.path.abspath(filename)], cwd='python-nuget')
+    os.remove(filename)
 
-    os.mkdir('pywin32')
-    rtn = subprocess.call(unzip_cmd() + [os.path.abspath(pywin32_filename)], cwd='pywin32')
-    assert rtn in [0, 1]
+    src_dir = os.path.join('python-nuget', 'tools')
+    python_exe = os.path.join(src_dir, 'python.exe')
+    check_call([python_exe, '-m', 'ensurepip', '--upgrade'])
+    check_call([python_exe, '-m', 'pip', 'install', 'pywin32==308'])
+    check_call([python_exe, '-m', 'pip', 'install', PSUTIL])
 
-    os.mkdir(os.path.join('python-nuget', 'lib'))
-    shutil.move(os.path.join('pywin32', 'PLATLIB'), os.path.join('python-nuget', 'toolss', 'Lib', 'site-packages'))
-
-    check_call(zip_cmd() + [os.path.join('..', '..', out_filename), '.'], cwd='python-nuget/tools')
+    check_call(zip_cmd() + [os.path.join('..', '..', out_filename), '.'], cwd=src_dir)
+    print('Created: %s' % out_filename)
 
     # cleanup if everything went fine
     shutil.rmtree('python-nuget')
-    shutil.rmtree('pywin32')
 
-    print('Created: %s' % out_filename)
     if '--upload' in sys.argv:
       upload_url = upload_base + out_filename
       print('Uploading: ' + upload_url)
@@ -153,11 +162,15 @@
     pybin = os.path.join(src_dir, 'install', 'usr', 'local', 'bin', 'python3')
     pip = os.path.join(src_dir, 'install', 'usr', 'local', 'bin', 'pip3')
     check_call([pybin, '-m', 'ensurepip', '--upgrade'])
+    # TODO: Potential bug: the following cmdline does not pin down a version
+    # of requests module, resulting in possibly different version of the module
+    # being installed on future runs. Switch to pip install requests==<version> to
+    # to download a pinned version.
     check_call([pybin, pip, 'install', 'requests'])
 
     # Install psutil module. This is needed by emrun to track when browser
     # process quits.
-    check_call([pybin, pip, 'install', 'psutil'])
+    check_call([pybin, pip, 'install', PSUTIL])
 
     dirname = 'python-%s-%s' % (version, revision)
     if os.path.isdir(dirname):