CrossBoot: จาก Pain Point ตัวเอง สู่ Tools สร้าง USB Bootable Windows สำหรับชาว macOS

เบื้องหลังการสร้าง CrossBoot โปรแกรมสร้าง USB Bootable Windows บน macOS แก้ปัญหาไฟล์เกิน 4GB และเทคนิค Cross-Compile wimlib แบบ Static Linking

10 Feb 20266 min read
CrossBoot: จาก Pain Point ตัวเอง สู่ Tools สร้าง USB Bootable Windows สำหรับชาว macOS

เรื่องมันเกิดจาก มี MacBook กับ Desktop PC เครื่องนึง ซึ่งเครื่อง Desktop เดี๋ยวลง Windows เดี๋ยวทำ Hackintosh แล้วแต่อารมณ์ เวียนว่ายตายเกิดอยู่อย่างนั้น LMAO (SSD TBW น่าจะเต็มเอาสักวัน) ไอ้จังหวะจะลง Hackintosh ไม่เท่าไหร่ ทำบน MacBook ได้เลย ชิลลล แต่พอจะลง Windows นี่ดิ หยาบสุดดด! หันซ้าย หันขวา จะทำตัว Windows Setup Bootable ยังไงดี!?

เมื่อ "ตัวท็อป" ในตลาด เทกระจาดกันหมด

โอเค ระดับ macOS แล้ว เจ้าตลาดเยอะแยะ ไหนดูซิ๊...

เปิดมาก่อนเลยตัวดีตัวดังฝั่ง macOS นั่นคือ balenaEtcher ใครจะเขียน ISO ต้องคุ้นชื่อนี้บ้างแหละ โอเค เตรียมไฟล์ Windows ISO ลงแอปเรียบร้อย เสียบ USB เข้าไป ไหนดูซิ๊ ผ่าม...

It looks like you are trying to burn a Windows image. Unlike other images, Windows images require special processing...

เอ้า พี่แกอยู่มานาน กลับไม่รองรับการเขียน Windows Setup Bootable บน macOS ซะงั้น

เคเลยยย ได้ งั้นไปดูตัวอื่น Ventoy ของดีชาว Linux... ปุ่ม Download สำหรับ macOS อยู่ไหน...??? ไม่มี!

ไม่เป็นไร มี Rufus ตัวดัง ไม่รู้จักนี่เชยยยยย (หยอกกกกกกกก)...

rufus-4.12.exe
rufus-4.12p.exe
rufus-4.12_x86.exe
rufus-4.12_arm64.exe
...

macOS อยู่ไหนนนนน!!! 🤦🏻‍♂️

ส่วน Tools ติดเครื่องอย่าง Bootcamp ก็ไม่สามารถเปิดขึ้นมาใช้งานบน Mac Apple Silicon ได้เลย

เมื่อไม่มีให้ใช้ ก็ต้องไปท่าเดิม (Terminal)

ในเมื่อ GUI ไม่มี ก็ต้องกลับสู่สามัญด้วย Command Line

Step 1: เตรียมอาวุธ

ขั้นแรกลง HomeBrew (ฉายา "The Missing Package Manager for macOS")

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

เช็คหน่อยว่าลงเรียบร้อยดีไหม

brew -v

ลง HomeBrew เสร็จแล้วก็ลง wimlib ต่อ

brew install wimlib

เช็ค wimlib สักหน่อย ถ้าขึ้น Option ต่าง ๆ แสดงว่าลงเรียบร้อยดี

winlib --help

Step 2: เตรียม USB และ ISO

จากนั้นก็ไป Format USB ให้เรียบร้อย จะผ่าน Disk Utility หรือ Terminal ก็ได้ตามสะดวกเลย แต่ต้อง Format เป็น MS-DOS (FAT32) นะ

จากนั้นก็ไป Mount ไฟล์ ISO Windows ที่เราเตรียมไว้ ดับเบิ้ลคลิกที่ไฟล์ ISO ได้เลย ก็จะเห็นไฟล์ต่าง ๆ ใน ISO

Step 3: ปัญหาโลกแตก "ไฟล์เกิน 4GB"

ถามว่าคัดลอกทั้งหมดไปเลยได้ไหม ตอบเลยว่า ไม่!! เพราะไฟล์ install.wim มันใหญ่เกิน 4GB ซึ่ง FAT32 รองรับไฟล์ขนาดไม่เกิน 4GB

แล้วทำไม่ใช้ ExFAT? เพราะแทบทุกบอร์ดไม่สามารถ Boot จาก ExFAT ได้ เอาจริงมันก็มีวิธีทำ ExFAT ให้ Boot ได้แหละ แต่ต้องทำ Bootloader เพิ่มเติม ซึ่งมันจะไม่ผ่าน Secure Boot โดย Defualt ซึ่งไม่ต้องการแบบนั้น

แล้วต้องทำไง?

บ้าน ๆ เลย ก็นั่งคัดลอกทุก File, Folder ยกเว้น install.wim ที่อยู่ในโฟลเดอร์ sources ไปลง USB ให้เรียบร้อยก่อน ถ้าขี้เกียจคัดลอกเองก็ใช้คำสั่ง

rsync -avh --exclude=sources/install.wim /Volumes/[ISO_NAME]/ /Volumes/[USB_NAME]/

มันก็จะคัดลอกทุกอย่างไปให้ ยกเว้นไฟล์ install.wim ที่มีขนาดใหญ่เกิน 4GB

จากนั้นก็มาจัดการกับไฟล์ install.wim กันต่อ

wimlib-imagex split /Volumes/[ISO_NAME]/sources/install.wim /Volumes/[USB_NAME]/sources/install.swm 3800

มันก็จะทำการ Split ไฟล์ install.wim ให้เป็นไฟล์ย่อย ๆ ขนาดไม่เกิน 3800MB แล้วก็ไปวางไว้ในโฟลเดอร์ sources ของ USB ให้เรียบร้อย เป็นอันเสร็จ

แต่นับดูว่ากี่ขั้นตอน แถมจะพิมพ์ผิดพิมพ์ถูกอีก ยิ่งถ้าเป็น End User นะ แค่เห็น Terminal ก็โบกมือบ้าย บายแล้ว 👋🏻

ใครขยันทำพิมพ์ผ่าน Terminal เองตลอด ๆ ก็เอาเลย But not me

Note: จริง ๆ จะมีวิธีใช้คำสั่ง dd เพื่อเขียน ISO ลง USB โดยตรง แต่ไม่แนะนำสำหรับ Windows ISO เพราะมันจะเป็น System Partition แบบ CD ทำให้หลาย ๆ บอร์ดไม่สามารถ Boot ได้

กำเนิด CrossBoot

ถ้าไม่อยากมานั่งทำผ่าน Terminal เองตลอด ก็ทำ GUI macOS App มีหน้าตาจิ้ม ๆ ทำเองให้หมด ไม่ต้องมานั่งพิมพ์เองให้เสียเวลา กดแล้วไปกินกาแฟรอเลย

โจทย์คือ ต้องรองรับ Secure Boot โดยไม่ต้องทำการเพิ่ม Secure Boot Key เองใน BIOS/UEFI ฉนั้นเราจะไม่ยุ่งกับ Bootloader เลย เลิกคิดไปใช้ ExFAT ได้เลย เราจะอยู่กับ FAT32 ที่เป็น Recommended และต้องรองรับการ Boot ทั้ง UEFI, Legacy BIOS แถมอีกเรื่องคือต้องทำ Windows 11 Requirements Bypass ได้

จริง ๆ ในใจอยากเขียนเป็น Native App แต่ก็ต้องใช้เวลาเรียนรู้ใหม่หมด เพราะไม่เคยจับ Swift เลย แถม Learning Curve มันก็ประมาณนึงแหละ แค่ SwiftUI ไม่พอ ต้องไปดูเรื่อง UIKit อีก (ขอสาป SwiftUI เหมือนภาษาที่เขียนไม่เสร็จสักที)

ด้วยความที่ตัวเองถนัดเรื่อง Web Dev อยู่เป็นทุนเดิม เลยเทใจไปให้ Electron (ที่ทุกคนรัก 55555555) ตอนเลือกก็ไม่ค่อยคิด พอมาใช้จริง Bundle Size ใหญ่ทะลุโลกมากกกกกก

Tech Stack & Design

Electron + React + SCSS ด้วยความใช้ React มาก่อน กับถนัดเขียน SCSS ก็เลยเลือกใช้สองตัวนี้แหละ เปิด Figma ขึ้นมา นึกแล้วนึกอีก จะ Design ออกมายังไงดี จะให้มันพ่นข้อความออกมาเยอะ ๆ สาย Geek น่าจะชอบ เห็นว่าทำอะไรอยู่ แต่คิดไปคิดว่ามันก็รกไปเปล่า ๆ เลยเอา Concept ของ Apple มาใช้

เน้นน้อย ๆ เรียบง่าย มีเท่าที่ต้องใช้จริง ๆ - Apple น่าจะไม่ได้กล่าว

จึงได้หน้าตาออกมาเป็น

ISO Selector

มีส่วนสำหรับเลือกไฟล์ ISO แต่คิดว่าถ้า User มีไฟล์ใน Finder ใกล้มืออยู่แล้ว ทำ Drop Zone ด้วยก็น่าจะง่ายดี ไม่ต้องไป Browse Finder อีก ด้วยที่ Electron มันก็คือ Chrome ตัวนึงนี่แหละ มันก็คือ Browser ฉบับย่อส่วน การทำงานทั้งหมดก็จะเหมือน Browser เลย ก็เขียนรับไฟล์มาปกติ... ไม่ได้ Path เต็ม เลยต้องใช้ Electron สื่อสารผ่าน IPC เพื่อให้ทำงานระดับ System Level ได้ ขอบคุณ Claude AI มาก ช่วยได้เยอะเลย

USB Selector

เลือก ISO เสร็จก็เลือก USB ที่ต้องการ ปัญหาเดิมเลย ต้องผ่าน IPC เพื่อจะได้รายชื่อ USB ที่เสียบอยู่ได้ แถมยังจะติด macOS Permission อีก ได้แล้วก็มาทำเป็น Drop Down ให้ User เลือกได้ง่าย ๆ

ทำ Auto Refresh เมื่อเสียบ USB หรือถอด USB ออกยังไง หาข้อมูลได้ก็ไปเจอตัว usb-detection แต่ด้วยที่ต้อง Compile แล้วมันไม่ผ่าน ทำไงก็ไม่ผ่าน (อาจจะเพราะเป็น Apple Silicon ด้วยมั้ง) สรุปก็เลยไม่ได้ใช้ ทำปุ่ม Refresh ให้ User กดเอาเองละกัน แหะ ๆ

Format and Create

ด้วยความที่อยากให้มันใช้งานง่าย เหมาะกับทุกเพศทุกวัย เลยยุบทุกอย่างให้เหลือปุ่มเดียว ทำงานรวดเดียวจบ เมื่อเลือก ISO กับ USB เสร็จแล้ว ค่อย Active ปุ่มให้ User กด Create

เมื่อกด Create ก็จะทำการ Format Disk ที่ต้องการให้เป็น FAT32 จากนั้นคัดลอกไฟล์ต่าง ๆ จาก ISO ไปยัง USB ให้เรียบร้อย ยกเว้น install.wim

Install.wim

เนื่องจากไฟล์ install.wim มีขนาดใหญ่เกิน 4GB ซึ่งเป็นข้อจำกัดของ FAT32 จึงต้องทำการ Split ไฟล์ install.wim ให้เป็นไฟล์ย่อย ๆ ก่อน โดยใช้ wimlib-imagex split

ตอนแรกก็สั่งง่าย ๆ คล้าย ๆ

wimlib-imagex split /Volumes/[ISO_NAME]/sources/install.wim /Volumes/[USB_NAME]/sources/install.swm 3800

ปัญหาที่เจอหลัก ๆ เลยคือ ด้วยที่ USB มันช้า ถึงแม้จะเป็น USB 3 ก็ตาม (เมื่อ USB Drive Cache เต็ม มันก็จะช้าลงสุด ๆ) ทำให้ wimlib มันทำการ Split แล้วหยุดรอ Disk I/O วนอยู่อย่างนั้น Split ก็ไม่เสร็จ เขียนก็ไม่เสร็จ

เลยเปลี่ยนให้ wimlib ทำการ Split ไว้ใน Temp Directory ก่อน แล้วค่อยคัดลอกไฟล์ .swm ไปยัง USB ทีเดียว ทำให้ Split ได้เร็วมาก ๆ หลังจากนั้นก็รอเขียนอย่างเดียว

Progress Bar

แน่นอนว่ายังไงก็ต้องมี Progress Bar ให้ User ดูว่ามีการเขียนไฟล์อยู่กี่ % แล้ว ไม่ใช่ว่าค้างไปดื้อ ๆ โดยแบ่งเป็น 2 ช่วงหลัก ๆ คือ Split และ Write

ช่วง Split จะใช้เวลาไม่นานจะให้ Progress Bar อยู่ในช่วง 0-15% ซึ่งเราสามารถดึงค่าการ Split จาก wimlib ได้โดยตรง ส่วนช่วง Write จะให้เป็น 15-100% เนื่องจากใช้เวลาพอสมควร

Cleanup

แน่นอนว่าการเขียนลง Temp Directory หาก User ยกเลิกกลางทาง หรือ App Crash กลางทาง หรืออะไรก็ตาม จะทำให้ File ที่ Split ไว้ใน Temp Directory ค้างอยู่ ซึ่งจะกินพื้นที่มาก ๆ แถม User เข้าไปลบยากด้วยเนื่องจากเป็น Hidden Folder และอยู่ใน Directory ที่ไม่คุ้นเคย

จึงต้องมีการจัดการ Cleanup หลังการใช้งานทุกครั้ง แต่การ Cleanup หลังจากทำงานเรียบร้อยยังไม่พอ ไหนจะกรณีที่ User ทำการ Force Close หรืออะไรก็ตามระหว่างทางมันจะไม่เข้า Cleanup Function จึงต้องทำการ Cleanup ทุกครั้งที่เปิดใช้งานด้วย

Deep Dive วิธี Cross-Compile wimlib แบบ Static Linking

แน่นอนว่าการจะ Split ไฟล์ install.wim ยังไงก็หนีไม่พ้นต้องใช้ winlib ซึ่ง pre-build บน macOS มีให้ใช้ผ่าน HomeBrew เท่านั้น ส่วนหน้าเว็บจะมีเฉพาะ Windows

โอเค ไม่ยาก ลงใน HomeBrew แล้วไปจิ๊กไฟล์ bin ออกมา ไม่ยากใช่มั้ยล้าาาาา... ตอบเลยว่าทำแบบนี้ไม่ได้ เนื่องจาก bin ที่ดึงออกมาจาก HomeBrew ตรง ๆ จะเป็น Dynamic Library ซึ่งมันจะผูก Linking ไปยัง winlib HomeBrew ของเครื่องที่ไปดึง winlib มา ตอนแรกไม่รู้เรื่อง แจกให้คนอื่นลองใช้ แตกกกกก

บนหน้าเว็บ wimlib จะมี Source Code แล้วมา Compile เอง ให้โหลดมาเตรียมไว้เลย แตกไฟล์ให้เรียบร้อย แล้วเปิด Terminal เข้าไปใน Directory ที่แตกไฟล์แล้ว หรือจะดึง Source Code ผ่าน Git ก็ได้

git clone https://wimlib.net/git/wimlib
cd wimlib

เตรียม Environment สำหรับ Compile ตัว wimlib

brew install autoconf automake libtool pkg-config

Compile สำหรับ Apple Silicon

สร้าง Config (Bootstrap) คำสั่งนี้จะไปอ่านไฟล์ตั้งต้น แล้วสร้างไฟล์ configure ให้

./bootstrap

Clean Build เดิม ล้างค่า config เก่าก่อนเสมอ เพื่อกันความผิดพลาด

make distclean

Configure & Compile

./configure \
    ./configure \
  --host=arm-apple-darwin \
  --disable-shared \
  --enable-static \
  --without-ntfs-3g \
  --without-fuse \
  --without-libarchive \
  CFLAGS="-arch arm64 -mmacosx-version-min=11.0"

make -j$(sysctl -n hw.ncpu)

ตรงนี้จะ Complie ลงไปต่ำกว่า macOS 11 ไม่ได้ (ได้ก็ไม่มีประโยชน์) เพราะ Mac ที่เป็น Apple Silicon เปิดตัวมาพร้อมกับ macOS 11

--disable-shared --enable-static คือ Flag ที่สั่งให้ตัว compile ไม่สร้าง Dynamic Library (.dylib) แต่ให้ฝัง Library ที่จำเป็นเข้าไปในตัวโปรแกรมเลย (Static Linking) ทำให้เอาไปรันเครื่องอื่นได้โดยไม่ต้องลง lib เพิ่ม

จะได้ไฟล์ wimlib-imagex ออกมา แนะนำเปลี่ยนชื่อก่อนเป็น wimlib-arm64 เดี๋ยวเราจะรวม arm กับ x86_64 ด้วยกันอีกที

mv wimlib-imagex wimlib-arm64

Compile สำหรับ Intel (x86_64)

Clean Build เดิมอีกครั้ง

make distclean

Configure & Compile

./configure \
  --host=x86_64-apple-darwin \
  --disable-shared \
  --enable-static \
  --without-ntfs-3g \
  --without-fuse \
  --without-libarchive \
  CFLAGS="-arch x86_64 -mmacosx-version-min=11"

make -j$(sysctl -n hw.ncpu)

ตรงนี้จะ Compile ให้ต่ำกว่า macOS 11 ก็ได้ แล้วแต่จะเอาไปใช้งานเลย เพื่อให้ครอบคลุมเครื่องรุ่นเก่าๆ

แนะนำเปลี่ยนชื่อก่อนเป็น wimlib-x86

mv wimlib-imagex wimlib-x86

รวมร่างด้วย Lipo (Creating Universal Binary)

ตอนนี้เรามี binary 2 ตัว แยกกันทำงาน เราจะจับมารวมเป็นไฟล์เดียวที่เรียกว่า "Fat Binary" หรือ Universal Binary

lipo -create -output wimlib-imagex wimlib-arm wimlib-x86

ตรวจสอบความเรียบร้อยของ binary ว่าเป็น Universal Binary หรือไม่

file wimlib-imagex
# ควรได้ผลลัพธ์: Mach-O universal binary with 2 architectures: [x86_64] [arm64]

และที่สำคัญคือ ต้องตรวจสอบว่า binary ที่ Compile ออกมาเป็น Static Linking หรือไม่

otool -L wimlib-imagex
# ควรเจอแค่: /usr/lib/libSystem.B.dylib

ถ้าได้ตามนี้คือเรียบร้อย ยินดีด้วย จบการ Cross-Compile และทำ Static Linking แล้ว แค่นี้ก็สามารถเอา Binary นี้ไป Bundle ใน App ได้เลย

ล่าสุดย้ายไป SwiftUI แล้ว Build ออกมาเล็กลงเยอะจาก Electron ~100MB เหลือ 5MB

ไปลองเล่นกันได้ที่

CrossBootpublic

A simple tool for creating Windows bootable USB drives on macOS. Supports Secure Boot, UEFI, Legacy BIOS, and Windows 11 bypass.

Swift
21

Latest Blog Posts

Mock Data: เมื่อ Framer Motion ต้องการ Client Component ร่วมกับ Next.js 13

Mock Data: เมื่อ Framer Motion ต้องการ Client Component ร่วมกับ Next.js 13

เลือกใช้ Next.js เพื่อจะใช้ประโยชน์จาก Server Component แต่ Framer Motion ดันต้องการ Client Component ในการทำ Animation จะทำอย่างไรดี?

Mock Data: รีวิวฝึกงานที่ ClickNext - Frontend Developer

Mock Data: รีวิวฝึกงานที่ ClickNext - Frontend Developer

ประสบการณ์การฝึกงานที่ ClickNext ในตำแหน่ง Frontend Developer ประสบการณ์ไม่มีวันลืม เป็นอย่างไร มีอะไรได้เรียนรู้บ้าง มาดูกัน!