Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CMF-Watch-Pro-2-BLE-Protocol — بروتوكول BLE مُهندس عكسيًا لـ CMF Watch Pro 2، مع توثيق تخطيط GATT، إطارات أوامر مشفرة بـ AES-128-CBC، مصافحة توثيق، ومزامنة بيانات الصحة لتطوير تطبيق رفيق بديل. | Kitploit
أدوات/GitHubGitHub/joshuapassos/cmf-watch-pro-2-ble-protocol
أمان الأنظمة المدمجةأمن البلوتوثأمان إنترنت الأشياءالهندسة العكسيةأمن الشبكات اللاسلكيةالتشفيرأمن الجوالأمان الأجهزة وإنترنت الأشياءتحليل البرامج الثابتة

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة
GitHubjoshuapassos/cmf-watch-pro-2-ble-protocol

CMF-Watch-Pro-2-BLE-Protocol

بروتوكول BLE مُهندس عكسيًا لـ CMF Watch Pro 2، مع توثيق تخطيط GATT، إطارات أوامر مشفرة بـ AES-128-CBC، مصافحة توثيق، ومزامنة بيانات الصحة لتطوير تطبيق رفيق بديل.

عرض المستودعالموقع الإلكتروني
328منذ شهر واحدلم تتم المراجعة بعد

CMF Watch Pro 2 — بروتوكول BLE (هندسة عكسية)

غير رسمي. يصف هذا المستند بروتوكول البلوتوث منخفض الطاقة (BLE) الخاص بساعة CMF Watch Pro 2 (CMF by Nothing)، الذي أُعيد بناؤه عبر الهندسة العكسية لتطبيق مصاحب بديل. لا يرتبط هذا المستند بـ Nothing/CMF ولا يحظى بتأييدها. استخدمه على مسؤوليتك الخاصة.

جميع الأعداد الصحيحة متعددة البايت في ترويسة الإطار ورموز التشغيل تكون بالترتيب الكبير (big-endian). الأعداد الصحيحة داخل حمولات الأوامر تكون بالترتيب الصغير (little-endian) ما لم يُذكر خلاف ذلك (وهذا يعكس برنامج الساعة الثابت) — انتبه للاستثناءات (GOALS_SET، GPS_PUSH، إزاحة/طول النقل الجماعي تكون بالترتيب الكبير).

علامات الثقة

كل ادعاء غير بديهي أدناه مُوسوم بكيفية إثباته:

  • ✅ تم التحقق على الجهاز — لوحظ في التقاط حي مُفكك التشفير أو تم اختباره ضد ساعة حقيقية.
  • 🔎 من البرنامج الثابت / هندسة APK العكسية — استُخرج بفك تجميع البرنامج الثابت (1.0.0.73) أو تطبيق APK الرسمي (3.5.7)؛ متسق مع الكود لكنه غير مُختبَر وقت التشغيل.
  • 🟡 مُثبت جزئيًا — مُثبت بنيويًا (دون اتصال، عبر المجموعة الكاملة، أو بالهندسة العكسية) لكن الخطوة المتبقية تتطلب الساعة ولم تُنفَّذ.
  • ⚠️ [غير مؤكد] — استنتاجي، غير مُثبت؛ قد يكون خاطئًا.

حيثما يصحح قسم لاحق قسمًا سابقًا، يُحتفظ بالنص السابق مع مؤشر بدلًا من حذفه — معرفة القراءات التي جُرّبت ودُحضت توفر على الشخص التالي نفس الالتفاف.

جهاز الاختبار لجميع الالتقاطات: CMF Watch Pro 2-5485، البرنامج الثابت 1.0.0.73، الرقم التسلسلي CI04102520008192، MCU Actions ATS3089C (Cortex-M4)، الشاشة 466×360.


1. تخطيط GATT

الهاتف هو عميل GATT؛ الساعة هي الطرف المحيط، وتُعلن عن نفسها باسم CMF Watch Pro 2-XXXX (4 أحرف سداسية عشرية).

فعّل الإشعارات بكتابة 01 00 إلى كل CCCD (00002902-…). قناة الأوامر (fff1/fff2) تحمل البروتوكول المؤطَّر أدناه. قناة الصدفة (77d4…) تحمل نصًا عاديًا بنمط AT (مثل AT GETSECRET؛ انظر §14). قناة البيانات (02f0…) تحمل كتلًا ثنائية كبيرة (واجهة الساعة، البرنامج الثابت، AGPS)، تُنسَّق عبر رموز تشغيل تحكم على قناة الأوامر.

معرّفات الخدمة UUID — تُعلن الساعة عن حوالي 10 خدمات أساسية. جرى تعدادها على وحدة حقيقية: 0xfff0 (الأوامر)، 0x180f (البطارية)، 0x180a (معلومات الجهاز)، 0xefe7، 0xffd0، 02f00000-…ffe0 و02f00000-…fe00 (البيانات)، 77d4e67c-2fe2-2334-0d35-9ccd078f529c (الصدفة / الاقتران)، e49a3001-f69a-11e8-8eb2-f2801f1b9fd1، f48a23c0-f69a-11e8-8eb2-f2801f1b9fd1.

⚠️ معرّف خدمة الصدفة هو 77d4e67c-…، وليس 77d4ff00-…. افترضت المراجعات السابقة لهذا المستند أن الخدمة تشترك في بادئة ff00 لخصائصها (77d4ff01/77d4ff02، §14) — لكنها لا تفعل، على الأقل على الوحدة التي فُحصت عليها (نتيجة من freethinkel/fmc، انظر §المصادر). معرّفات الخصائص لم تتغير. لم يُتحقق مما إذا كان 77d4e67c ثابتًا عبر الوحدات — عدِّد بدلًا من الترميز الثابت.

🌐 ملاحظة حول Web Bluetooth. لا يكتشف Chromium إلا الخدمات التي أدرجتها الصفحة في optionalServices، حتى عند استدعاء getPrimaryServices() غير المفلتر — صفحة تُدرج 3 خدمات ترى 3، بينما يُظهر chrome://bluetooth-internals (طبقة C++ الخاصة بـ Chrome، غير مُقيَّدة) كل الـ 10. إذا كتبت عميل متصفح، أدرج كل معرّف UUID أعلاه مسبقًا أو سيفشل الاقتران مع خدمات موجودة بوضوح. لا يوجد Web Bluetooth في Firefox/Safari؛ يتطلب إيماءة مستخدم + HTTPS/localhost.

✅ جرى تشغيل جلسة حقيقية كاملة على قناة الأوامر الواحدة — خلال التقاط استخدام كثيف لمدة 160 ثانية لم يكن هناك أي حركة مرور على قنوات البيانات/البرنامج الثابت أو الصدفة باستثناء أثناء نقل OTA/واجهة ساعة صريح.


2. تنسيق الإطار (0xF5)

كل رسالة على قناة الأوامر مغلفة في إطار واحد أو أكثر بترويسة من 11 بايت:``` +------+-----------+--------+-------------+-------------+--------+-------------------+ | 0xF5 | chunkLen | cmd1 | chunkCount | chunkIndex | cmd2 | chunk bytes … | | 1 B | 2 B (BE) | 2 B BE | 2 B BE | 2 B BE | 2 B BE | chunkLen bytes | +------+-----------+--------+-------------+-------------+--------+-------------------+ __________________________ 11-byte header ____________________________/

root@kitploit:~
- يشكّل `cmd1`/`cmd2` معًا **رمز العملية** (انظر §6). 🔎 تم التحقق منه مقابل مُنشئ الإطارات في التطبيق الرسمي
  (`C6117b.m30831g`).
- `chunkCount` = إجمالي الأجزاء لهذا الأمر؛ `chunkIndex` يبدأ من **1**.
- `chunkLen` = عدد بايتات `chunk` في هذا الإطار.
- قد يتم تجزئة كتابة BLE واحدة بواسطة MTU للرابط؛ يقوم المستقبِل بتخزين البايتات الخام مؤقتًا و
  إعادة استخراج الإطارات الكاملة. يتم تقسيم الحمولات الكبيرة إلى أجزاء متعددة (نفس `cmd1/cmd2`،
  مع زيادة `chunkIndex`) وإعادة تجميعها بالترتيب.

### اصطلاح رمز العملية (✅ تم تأكيده على الشبكة)

- `cmd1 = 0xFFFF`: `cmd2` في `0x80xx`/`0x90xx` = من الهاتف إلى الساعة (طلب/تعيين)؛ `0x00xx`/`0xa0xx` =
  من الساعة إلى الهاتف (رد). تتطابق الأزواج بالبايت المنخفض (`0x9055`↔`0xa055`، `0x8051`↔`0x0051`).
- `cmd1` خاص بالميزة: لاحقة `cmd2` = `0x0001` **SET**، `0x0002` **GET**، `0x0003` **ACK**.

### جسم الجزء

لكل جزء، الجسم هو `payloadPiece ‖ CRC32_LE(payloadPiece)` (CRC من 4 بايتات، بترتيب البايت الأقل أهمية،
zlib/IEEE). إذا كان الأمر **مشفّرًا** (انظر §3)، يتم بعد ذلك تشفير `payloadPiece ‖ CRC` بالكامل
باستخدام AES-128-CBC/PKCS7 ويصبح هذا النص المشفر هو `chunk` للإطار.

**خصوصية النص الصريح:** لرموز العمليات غير المشفرة، *تحسب* الساعة CRC من 4 بايتات في `chunkLen` ولكنها
**لا** ترسله. لذا عند فك تشفير إطار غير مشفر، يكون طول البيانات الفعلي هو `chunkLen − 4`.
(تحمل الإطارات المشفرة CRC داخل النص المشفر كالمعتاد.)

حجم الأجزاء (بحيث تقع الأجزاء المشفرة على حدود كتل AES)، مع `maxWrite = mtu − 3`:
- مشفّر: `floor((maxWrite − 11) / 16) * 16 − 4 − 1`
- غير مشفّر: `maxWrite − 11 − 4 − 1`

✅ كانت جميع قيم `chunkLen` المرصودة للإطارات المشفرة مضاعفات للعدد 16 (محاذاة الكتل صحيحة).

---

## 3. الأوليات التشفيرية

- **AES-128-CBC** مع حشو **PKCS7** و**IV ثابت** (من البرنامج الثابت
  `CmfCharacteristic.AES_IV`):
  `50 51 52 53 54 55 56 57 60 61 62 63 64 65 66 5A`.
- **CRC32** (zlib/IEEE)، يُرسل كـ 4 بايتات بترتيب البايت الأقل أهمية.
- **SHA-256** على تسلسل الأجزاء.

اشتقاق المفتاح:```
authkey      = SHA256( rnd1 ‖ rnd2 ‖ secret )[0..16]      // persisted across sessions
sessionKey   = SHA256( nonce ‖ authkey )[0..16]           // per connection
  • secret = سر الجهاز (16 بايت) (يمكن الحصول عليه من الساعة عبر أمر الصدفة AT GETSECRET → GETSECRET:<32-hex>,OK).
  • rnd1 = 16 بايت عشوائي يختارها الهاتف؛ rnd2 = 16 بايت عشوائي من الساعة.
  • nonce = بايتات من رد nonce الخاص بالساعة.

بعد تعيين المفتاح، جميع إطارات قناة الأوامر مشفرة بـ AES باستثناء رموز التشغيل النصية العادية المدرجة في §5.

✅ تم التحقق من كلا الاشتقاقين: تم استرداد authkey من ntwatch.db لهاتف مُجذّر وتطابق مع القيمة المشتقة من rnd1/rnd2/secret الملتقطة؛ وتم إعادة إنتاج sessionKey من nonce ملتقط يفك تشفير الإطارات الحية.


4. مصافحة المصادقة / الاقتران

يشارك مسارا الدخول نفس ذيل nonce/confirm.

4.1 الاقتران لأول مرة (امتلاك سر الجهاز)```

phone → (shell) AT GETSECRET watch → (shell) GETSECRET:<32hex>,OK phone: rnd1 = random16 ; signed1 = SHA256(rnd1 ‖ secret) phone → AUTH_PAIR_REQUEST (plaintext) payload = rnd1(16) ‖ signed1(32) // 48 B watch → AUTH_PAIR_REPLY (plaintext) payload = rnd2(16) ‖ signed2(32) // 48 B phone verifies signed2 == SHA256(rnd2 ‖ secret) phone: authkey = SHA256(rnd1 ‖ rnd2 ‖ secret)[0..16] → set crypto key = authkey phone → AUTH_PHONE_NAME (encrypted) payload = 0xA5 ‖ model(UTF-8) // e.g. "CMF Watch Pro 2" watch → AUTH_WATCH_MAC (encrypted) phone → AUTH_NONCE_REQUEST (encrypted) payload = 0xA5 watch → AUTH_NONCE_REPLY (encrypted) payload = nonce phone: sessionKey = SHA256(nonce ‖ authkey)[0..16] → set crypto key = sessionKey phone → AUTHENTICATED_CONFIRM_REQUEST (encrypted) payload = 0xA5 watch → AUTHENTICATED_CONFIRM_REPLY (encrypted) → state = Initialized

root@kitploit:~
عند حدوث `AUTH_FAILED (0xFFFF,0xA061)` أو عدم تطابق التوقيع، يفشل المصادقة.

### 4.2 إعادة الاتصال (مفتاح المصادقة معروف بالفعل)```
        set crypto key = authkey (persisted)
phone → AUTH_PHONE_NAME      (encrypted)  payload = 0xA5 ‖ model
watch → AUTH_WATCH_MAC       (encrypted)
phone → AUTH_NONCE_REQUEST   (encrypted)  payload = 0xA5
watch → AUTH_NONCE_REPLY     (encrypted)  payload = nonce
        sessionKey = SHA256(nonce ‖ authkey)[0..16]   → set crypto key = sessionKey
phone → AUTHENTICATED_CONFIRM_REQUEST (encrypted)  payload = 0xA5
watch → AUTHENTICATED_CONFIRM_REPLY   (encrypted)  → Initialized

✅ تم رصد ترتيب إعادة الاتصال (بدون حركة مرور shell) سليمًا في التقاط حقيقي.

4.3 تهيئة ما بعد المصادقة (المرحلة 2)

⚠️→✅ TIME إلزامي قبل استعلامات البيانات. بعد Initialized، لن تستجيب الساعة لـ BATTERY أو SERIAL_NUMBER_GET أو مصافحة ACTIVITY_FETCH_* حتى يتم إرسال TIME (FFFF 8004) في الجلسة — وبدونها، يصل فقط FIRMWARE_VERSION_RET غير المطلوب وينتهي كل شيء آخر بوقت انتهاء. ✅ تم التأكيد مباشرة (Pixel 8a): إرسال طلبات GET الثلاثة بدون TIME → استجابة البرنامج الثابت فقط؛ إرسال TIME أولاً → البطارية والرقم التسلسلي يبدآن بالاستجابة.

الترتيب الموصى به للمرحلة 2: TIME → FIRMWARE_VERSION_GET → SERIAL_NUMBER_GET → BATTERY (0xA5) → دفع الإعدادات → مزامنة الصحة (§8).

4.4 نمط صدى GET → SET (✅)

لا يوجد كود تشغيل "قراءة" منفصل لمعظم الإعدادات. إرسال *_GET (cmd2 = 0x0002، الحمولة 0xA5) يجعل الساعة ترد بكود تشغيل SET (cmd2 = 0x0001) حاملًا القيمة الحالية. يتم تأكيد أوامر SET بـ cmd2 = 0x0003 وجسم فارغ.


5. نص عادي مقابل مشفر

الإطارات مشفرة بـ AES بمجرد تعيين مفتاح، باستثناء أكواد التشغيل هذه، التي تكون دائمًا نصًا عاديًا:

  • AUTH_PAIR_REQUEST (FFFF 8047)، AUTH_PAIR_REPLY (FFFF 0048)
  • DATA_CHUNK_WRITE_WATCHFACE (FFFF 9064)، DATA_CHUNK_WRITE_FIRMWARE (FFFF 9042)، DATA_CHUNK_WRITE_AGPS (FFFF 905F)

ترويسات الإطارات (cmd1/cmd2) تسافر دائمًا بشكل واضح، لذا فإن تسلسل الأوامر مرئي في أي التقاط حتى بدون المفتاح — فقط الحمولات المشفرة تحتاج إلى sessionKey.


6. مرجع أكواد التشغيل (cmd1, cmd2)

GET/SET/REQUEST = هاتف→ساعة؛ RET/REPLY/ACK/RESPONSE/DATA = ساعة→هاتف.

الجلسة / الجهاز

المصادقة

الإشعارات / المكالمات / البحث

الموسيقى

الاسمcmd1,cmd2
MUSIC_INFO_SET / _ACKFFFF 905C / FFFF A05C
MUSIC_BUTTONFFFF A05D

المنبهات / جهات الاتصال / التذكيرات

الإعدادات

الطقس

الاسمcmd1,cmd2
WEATHER_SET_1 (الذي يعمل)FFFF 906B
WEATHER_SET_2 (مُتجاهَل على Pro 2 — انظر §9)0066 0001

واجهات الساعة / الأقراص

الصحة / المزامنة

أكواد تشغيل JS فقط (FFFF 8051، FFFF 0051، FFFF 90A2، FFFF 90C5، FFFF A056، FFFF 908A/908B حالة ChatGPT/الدعم) تتم معالجتها في bytecode Hermes الخاص بالتطبيق، وليس في طبقة Java. تظهر ترويساتها في الالتقاطات لكن دلالات الحمولة ⚠️ [غير مؤكدة].

نقل البيانات بالجملة (قناة البيانات)

تستخدم واجهة الساعة / البرنامج الثابت / AGPS حلقة تهيئة → طلب/كتابة أجزاء → تأكيد إنهاء:

(كل cmd1 = FFFF.) تقود الساعة الحلقة بإصدار DATA_CHUNK_REQUEST_*(offset, length) (offset/length = u32 big-endian)؛ يرد الهاتف بـ DATA_CHUNK_WRITE_* حاملًا payload[offset..offset+length] على خاصية البيانات. انظر §11–§12 للتفاصيل.


7. الوقت والمنطقة الزمنية

حمولة TIME (FFFF 8004) = epochSeconds(i32, BE) ‖ utcOffsetMillis(i32, BE). تُرسل مباشرة بعد المصادقة حتى تعرض الساعة الوقت المحلي (وتفتح استعلامات البيانات — انظر §4.3).

⚠️ الطوابع الزمنية الصحية من الساعة بتوقيت UTC. يجب على التطبيق المصاحب إضافة إزاحة UTC المحلية قبل اشتقاق اليوم التقويمي المحلي / وقت اليوم. (تجميع الصحة حسب يوم UTC الخام يقلب اليوم في وقت محلي خاطئ.)

حمولة TIME_FORMAT (005F 0001) = 1 بايت: 00 = 24 ساعة، 01 = 12 ساعة.


8. مزامنة الصحة

  1. يرسل الهاتف ACTIVITY_FETCH_1؛ ترد الساعة بـ ACTIVITY_FETCH_ACK_1 (البايت الأول 01 ⇒ جاهزة).
  2. يرسل الهاتف ACTIVITY_FETCH_2؛ ثم تدفع الساعة دفعة من إطارات البيانات: ACTIVITY_DATA، HEART_RATE_*، SPO2، STRESS، SLEEP_DATA، WORKOUT_SUMMARY[_V3].
  3. يتم تحليل كل منها إلى عينات لكل دقيقة / جلسات وتجميعها حسب اليوم المحلي.

المزامنة تسلسلية (يجب أن تتبع TIME؛ تطلق الساعة التدفقات بعد ACK_2)، وليست دفعة واحدة. جلسة ثقيلة تدفع ~170–210 إطار إشعار في ~160 ثانية. ✅

8.1 سجل النشاط — ACTIVITY_DATA (32 بايت لكل منها، LE) ✅

وحدة السعرات الحرارية: يتم الإبلاغ عن سعرات النشاط بـ cal (سعرات غرامية). اقسم المجموع اليومي على 1000 للحصول على kcal. (سعرات ملخص التمرين، على النقيض، بالفعل بـ kcal.)

8.2 عينات HR / SpO₂ / الإجهاد ✅

  • HR اليدوي/التلقائي، HR التمرين، SpO₂، الإجهاد = 8 بايت لكل منها: timestamp(i32 LE) ‖ value(i32 LE) (القيمة = نبضة/دقيقة / نسبة SpO₂ / مؤشر الإجهاد).
  • HR أثناء الراحة (00DA 0001) مختلف — 5 بايت: timestamp(i32 LE) ‖ hr(u8). ✅ مثال مباشر 5e dc 29 6a 4e → ts، hr = 78 نبضة/دقيقة. نطاقات درجة الإجهاد: 1–29 / 30–59 / 60–79 / 80–99.

8.3 النوم — SLEEP_DATA (ترويسة 18 بايت + N × سجلات 8 بايت) ✅

SLEEP_DATA واحد = جلسة نوم واحدة؛ قد تحتوي الليلة على عدة جلسات (الاستيقاظات الدقيقة تقسم الجلسات).

الترويسة:

كل سجل 8 بايت: timestamp(u32) ‖ duration_s(u16) ‖ stage(u16). أكواد المرحلة: 1 = عميق، 2 = أساسي/خفيف، 3 = REM، 4 = مستيقظ. ✅ تم التحقق مقابل ليلة كاملة (جلستان، إجماليات D/C/R/A تتطابق).

8.4 ملخص التمرين — WORKOUT_SUMMARY v1 (54 بايت) / _V3 (0160 0001)

v1: start(u32)، end(u32)، duration_s(u32)، ثم النوع/السعرات/الخطوات/المسافة/متوسط HR وكتلة GPS/ممتدة. ✅ تم تأكيد تخطيط v1 مقابل البرنامج الثابت. WORKOUT_SUMMARY_V3 هو تخطيط أحدث لنفس البيانات بالإضافة إلى كتلة ممتدة ~40 بايت (exerciseLoad، هوائي/لاهوائي، recoveryTime، VO₂max، إيقاع، PAI، أفضل أوقات الجري…). مجموعة الحقول معروفة (من قاعدة بيانات Room الخاصة بالتطبيق) لكن إزاحات البايت الدقيقة داخل تلك الكتلة 40 بايت ⚠️ [غير مؤكدة] — إغلاقها يتطلب التقاطًا خامًا واحدًا لتمرين GPS.


9. حمولات أوامر مختارة

السلاسل UTF-8، مقتطعة بالبايت إلى حجم الحقل (قد يقسم الاقتطاع حرفًا متعدد البايت، مطابقًا لسلوك s.encode()[:max] في البرنامج الثابت)؛ الحقول القصيرة مبطنة بالأصفار على اليمين.

  • APP_NOTIFICATION (0065 0001) ✅: iconCode(1) ‖ 0x00 ‖ when(u32 BE) ‖ titleLen(1) ‖ title ‖ body. يحدد iconCode أيقونة التطبيق (WhatsApp=8، Telegram=12، Instagram=18، Gmail=27؛ غير معروف=0xFF). العنوان ≤ 20 بايت، الجسم ≤ 128 بايت. أُرسل من عميل → عرضته الساعة + ACK 0065 0003.
  • BATTERY (005C 0001) ✅: الرد = level(1) ‖ charging(1) (مثل 3b 00 = 59 %، غير مشحونة).
  • SERIAL_NUMBER_RET (00DE 0001) ✅: len(1) ‖ ASCII (مثل 10 + "CI04102520008192").
  • USER_INFO (0095 0001) ✅: height_cm(1) ‖ weight_kg(1) ‖ age(1) ‖ gender(1: 1=M) (مثل = 172 سم / 73 كجم / 31 / ذكر).

10. ملاحظات التنفيذ والخصائص الغريبة

  • لا ساعة نظام في برامج الترميز: تأخذ برامج الترميز now/utc_offset كمعاملات صريحة (حتمية، قابلة للاختبار). يوفر النقل الوقت الحقيقي.
  • TIME يبوّب كل شيء (§4.3) — أرسله أولاً أو تبقى الساعة صامتة في استعلامات البيانات.
  • عد CRC النص العادي (§2) سهل الخطأ — إطارات النص العادي تعلن عن CRC لكنها تحذفه.
  • ترتيب البايتات: الترويسة + أكواد التشغيل BE؛ أعداد صحيحة للحمولة LE؛ استثناءات — GOALS_SET و GPS_PUSH big-endian، وإزاحة/طول النقل بالجملة big-endian.
  • MTU: تُحسب أحجام الأجزاء بحيث تتم محاذاة الأجزاء المشفرة إلى كتل AES 16 بايت.
  • authkey قابل للحفظ (خزّنه بعد أول إقران)؛ sessionKey لكل اتصال و مشتق من nonce الساعة عند كل إعادة اتصال.

11. واجهات الساعة / الأقراص — الإنشاء

تدعم الساعة (أ) أقراص الصور/المخصصة (صورة خلفية + ساعة رقمية مرسومة بالبرنامج الثابت) و(ب) الأقراص المنظمة (أقراص مدمجة/من المتجر: خلفية بالإضافة إلى طبقات sprites موضوعة، عقارب، وأدوات نصية). ينتقل كلاهما عبر قناة البيانات عبر حلقة التهيئة → الأجزاء في §6.

ما يعمل فعليًا (✅ تم التحقق مباشرة): بناء قرص صورة من أي صورة وتثبيته؛ تثبيت أي من أقراص المتجر الـ 103 دون اتصال؛ إعادة تلوين قرص منظم (استبدال الخلفية أو أي sprite غير الخلفية) وتحريك طبقاته؛ إعادة ترتيب / تبديل القرص النشط؛ وبناء قرص منظم من الصفر — غلاف المشهد 0x20 مفكوك الترميز والمنشئ منفذ (§11.7)، مثبت دون اتصال لإعادة تدوير جميع أقراص المتجر الـ 103 بايتًا ببايت ولإصدار حاويات اصطناعية تجتاز مدقق البرنامج الثابت الخاص به. 🟡 الخطوة الوحيدة غير المثبتة هي مشاهدة عرض اصطناعي من الصفر على الجهاز عبر 9075 (الدليل الهيكلي دون اتصال يغطي بالفعل ما كان يسبب رفض 0a). لا يوجد حاجز برنامج ترميز أو نقل ولا حاجة لأدوات البائع. الادعاءات القديمة "العرض المنظم مخبوز في RES / مستحيل عبر BLE" و"برنامج ترميز cf=0x1f من جانب الخادم" كانت خاطئة (خطأ إزاحة+بايت لكل بكسل) — يعرض البرنامج الثابت الأقراص المنظمة مدفوعة بالبيانات من الملف الذي ترسله.

11.1 إدارة الأقراص — DIAL_COMMAND (9055 / a055) ✅

  • النوع 0 = الاستعلام عن القائمة. الرد a055 = result(u8) ‖ selectIndex(u8) ‖ total(u8) ‖ max(u8) ‖ N × dialId(u32 LE) ‖ ffffffff. مثال: 01 05 06 07 … = نشط #5، 6 أقراص، الحد الأقصى 7.
  • النوع 1 = إعادة ترتيب / تحديد النشط: أعد إرسال القائمة كاملة مع القرص المستهدف في الفهرس 0 (هكذا يبدّل التطبيق الرسمي الواجهات؛ لا يوجد كود تشغيل مخصص "تعيين نشط").
  • حذف قرص = أعد إرسال القائمة بدون معرفه.
  • CHANGE_DIAL (009F 0001) خامل على fw 1.0.0.73 (يعيد ثابتًا، لا يبدّل) — لا تستخدمه.

11.2 تدفق النقل ✅```

INIT1 8052 (payload A5) → 0052 [0]=01 INIT2 9063 (photo, APPEND) | 9075 (structured, REPLACE) → A063 / A075 [0]=01 [ watch → DATA_CHUNK_REQUEST A064 (offset, length; u32 BE, +progress u8) phone → DATA_CHUNK_WRITE 9064 (bytes[offset..offset+length], plaintext) ] × N FINISH A065 → 9065 (payload A5)

root@kitploit:~
Finish reply byte: `01` = تم التفعيل والحفظ؛ `0a` = تم التخزين لكن **لم** يتم التفعيل / مرفوض. على
أندرويد، يجب إرسال كل `DATA_CHUNK_WRITE` كـ **كتابة BLE واحدة لكل إطار** — دمج
وإعادة التقسيم حسب MTU يفقد مزامنة الرؤوس وتدور الساعة في طلب الإزاحة 0.

- **`9063` (صورة) = إلحاق.** قائمة الواجهات تنمو (6→7)؛ `watchfaceId = 0xFFFFFFFF` (قيمة
  مخصصة) بحيث لا يتم رفضها أبدًا كمكررة، وتقوم الساعة بتفعيلها تلقائيًا.
- **`9075` (منظم) = استبدال** خانة `old_id`. يجب أن يكون `old_id` **موجودًا بالفعل** في القائمة
  (وإلا `0a`). لإعادة تثبيت معرّف موجود بالفعل، **احذفه أولاً** (9055 قائمة ناقص المعرّف)
  ثم ارفع "جديدًا" — إعادة استخدام معرّف في مكانه تعطي `0a`.

### 11.3 واجهة صورة / مخصصة — ✅ تم التحقق منها بالكامل من البداية إلى النهاية

**الحاوية** (رحلة ذهاب وإياب تم التحقق منها بالبايت؛ جميع الحقول little-endian):```
0x00  magic     6c 8d c4 a5
0x04  count     12 00 00 00   (=18)  [constant, NOT an element count]
0x08  00 × 8
0x10  lenFull   u32 LE        (length of the whole FULL block: tag+len+payload)
0x14  FULL  tag 04 48 47 3a ‖ payloadLen(u32 LE) ‖ LZ4(RGB565-LE)   → 466×466  [raw 434312 B]
      THUMB tag 04 38 c4 21 ‖ payloadLen(u32 LE) ‖ LZ4(RGB565-LE)   → 270×270  [raw 145800 B]
EOF-4 magic     6c 8d c4 a5   [trailer = magic repeated]

Codec = معيار LZ4 block عبر RGB565 little-endian، من الأعلى إلى الأسفل (payloadLen يُحسب من أول بايت LZ4). يستخدم التطبيق الرسمي LZ4-HC ويجرد رأس/ذيل كتلة LZ4 البالغ 21 بايت؛ كما يعمل مُرمّز LZ4 عادي يعتمد على literals فقط — يقبل الساعة أي LZ4 صالح، ولا يُشترط تطابق البايتات. تُضبط البكسلات خارج الدائرة المنقوشة (المركز 233,233، نصف القطر 233) على 0x0000.

INIT_2 لـ 9063 — الرأس الدقيق (✅ هذا هو الذي يعمل):``` 01 ‖ size(u32 BE) ‖ FF FF FF FF ‖ 01 01 01 ‖ styleId(u16 BE) ‖ posX(u16 BE) ‖ posY(u16 BE) ‖ color565(u16 BE) ‖ FF × 8

root@kitploit:~
`size` = الطول الدقيق لملف `.bin`؛ `FFFFFFFF` = قيمة `watchfaceId` مخصصة؛ `styleId` من 0 إلى 4 يحدد
تخطيط الساعة الرقمية المدمج (يُرسَم دائمًا — لا يوجد خيار "إيقاف")؛ `posX/posY` يحددان موضعه (قيم معروفة
56 / 77)؛ `color565` يلوّنه (مثل `FFFF` = أبيض). ⚠️ الصيغة الأقصر `A5 ‖ size ‖ watchfaceId` **مرفوضة**
مع رمز الإنهاء `0a` — استخدم الترويسة الكاملة أعلاه. (التنفيذ المرجعي:
`core-rust/engine.rs::build_wf_init2`، بما يطابق `C6135t.m31104u` في التطبيق الرسمي.)

**الوصفة:** غيّر حجم الصورة إلى 466×466 (وصورة مصغرة 270×270)، وحوّلها إلى RGB565-LE من الأعلى للأسفل،
واختياريًا صفّر البكسلات خارج الدائرة، واضغط كلًا منها بـ LZ4، وجمّع الحاوية أعلاه، وارفعها
عبر خط أنابيب `9063` مع `watchfaceId = 0xFFFFFFFF`. (الترميز المرجعي: `core-rust/watchface.rs`،
`work/codec_dfa.py`.)

### 11.4 قرص منظم / متجر — الحاوية والترميزات ✅

**تخطيط الملف** — ترويسة الـ 36 بايت تتكرر **بايتًا ببايت كتذييل من 36 بايت** في نهاية الملف
(✅ تم التحقق على 15 قرصًا؛ يجب أن يرفض المحلل ملفًا يختلف فيه الاثنان):```
[36-byte header][scene TLV (§11.7)][asset pool][36-byte header again]

Header (بنية متطابقة عبر جميع أقراص المتجر الـ 103؛ جميع الحقول بنظام little-endian):``` 0x00 crc_tree u32 LE [CRC32-raw of header[0x04:0x24] ‖ scene section] ✅ see below 0x04 magic 01 00 00 XX [XX = 0x00 or 0x02; both seen, meaning of 0x02 unknown] 0x08 name char[16] [NUL-terminated, e.g. "SlopeTime", "Metaball"; may carry a non-zero tail after the NUL (@0x17) — round-trip it verbatim] 0x18 size_a u32 LE [= filesize − 36 = footer offset = header+body] ✅ 103 dials 0x1c size_b u32 LE [asset-pool length, exactly] ✅ 15 dials 0x20 crc_assets u32 LE [CRC32-raw of the asset pool] ✅ see below 0x24 … [body starts here: the 0x20 scene container, §11.7]

root@kitploit:~
> ⚠️ **تصحيح (يلغي "لا يوجد فحص حظر").** قراءات سابقة فسّرت `@0x00` على أنه
> معرّف/تجزئة لكل حوار و`@0x20` على أنه "3× كلمات u32 للمعرّف/التجزئة \[ليست CRC]"، وذكرت أن CRC32/Adler32/
> مجموع البايتات جميعها تفشل في المطابقة. كلتا الكلمتين **هما CRC32** — الاختبارات السابقة فاتتها لأن
> النمط غير قياسي، ولأن قراءة "3 كلمات عند `0x20`" كانت تخلط بين كلمة CRC الواحدة
> وأول بايتات حاوية المشهد التي تبدأ عند `0x24` (وكذلك "الاسم المكرر عند
> `0x2c`" هو عقدة اسم المشهد `0x86`، §11.11). الاكتشاف من
> [freethinkel/fmc](https://github.com/freethinkel/fmc)؛ تم التحقق منه مجددًا هنا.

**CRC32-raw** = متعدد الحدود IEEE المنعكس `0xEDB88320`، **`init = 0`**، و**بدون XOR نهائي** — أي
ليست `init=0xFFFFFFFF` ولا `^0xFFFFFFFF` الخاصة بـ `crc32` القياسي. هذا هو السبب الكامل
وراء عدم تطابق CRC32 الجاهز أبدًا. لاحظ اعتماد الترتيب: `crc_assets` يقع داخل النطاق
المغطى بواسطة `crc_tree`، لذا **اكتب `@0x20` أولاً، ثم احسب `@0x00`**.```python
def crc32_raw(data: bytes) -> int:          # tab = standard 0xEDB88320 reflected table
    c = 0                                    # init 0, no final inversion
    for b in data: c = tab[(c ^ b) & 0xFF] ^ (c >> 8)
    return c & 0xFFFFFFFF

crc_tree   = crc32_raw(f[0x04:0x24] + f[0x24:first_asset])
crc_assets = crc32_raw(f[first_asset:len(f)-36])

تم التحقق: 9/9 من أقراص المتجر الأصلية تتطابق على كلتا الكلمتين، و6/6 من قوالب هذا المستودع الخاصة تتطابق على crc_tree.

🟡 يبدو أن البرنامج الثابت لا يفرض أيًا من CRC. كل قرص ثبّته هذا المستودع فوق 9075 — بما في ذلك إعادة التلوين الناتجة عن مسار التعديل الموضعي بنفس البصمة (§11.6)، الذي يغيّر حمولات الأصول وبايتات X/Y دون إعادة حساب الترويسة — عُرض بشكل سليم على الجهاز. لذا فإن CRC قديم ليس هو ما يسبب رفض 0a (ذلك هو ثابت نافذة الحاوية، §11.7). تعامل مع حقول CRC على أنها اكتبها صحيحة على أي حال: رخيصة، وهي حقل النزاهة الوحيد المعروف في الصيغة. أي شيء يعيد كتابة المشهد أو مجموعة الأصول يجب أن يعيد حساب الكلمتين.

أقراص Stub (~173 بايت، مثل المعرّفات 273/274/277) هي عناصر نائبة لوجوه مدمجة في ROM: ترويسة + دليل، بدون أصول حقيقية.

الأصول — كل منها هو dimsWord(u32 LE) ‖ len(u32 LE) ‖ LZ4(payload)، حيث cf = dimsWord & 0x1f، w = (dimsWord >> 10) & 0x7FF، h = (dimsWord >> 21) & 0x7FF، وlen يُحسب من أول بايت LZ4 (الـ 1f 00 01 00 الذي تراه غالبًا هناك هو أول رمز LZ4 — لا تتخطّه). الحجم بعد فك الضغط = w·h·bpp:

✅ جميع الأصول 4151/4151 عبر الأقراص الـ 103 تُفك ترميزها بدقة باستخدام مفكك ضغط lz4.block قياسي عند w·h·bpp. الشفافية هي بايت ألفا (cf=5/24) أو 0x0000 (cf=4 خارج الدائرة) — لا يوجد RLE ولا "هروب". الترميز = إعادة إنشاء البيانات النقطية → LZ4 قياسي → [dimsWord][len][LZ4].

INIT_2 لـ 9075 — جسم مشفّر بـ AES:``` kind(1) ‖ old_id(u32 LE) ‖ new_id(u32 LE) ‖ file_len(u32 LE)

root@kitploit:~
`kind` = `0x02`/`0x03`؛ `old_id` = الاتصال النشط الحالي (من `9055`)؛ `file_len` = الحجم الحقيقي لملف
`.bin` (= `@0x18 + 36`). تثبيت ملف `.bin` من المتجر كما هو هو المسار المضمون (معرّف Ring Data 359 +
102 أخرى مؤكدة). (المرجع: `core-rust/engine.rs::build_dial_replace_init`.)

### 11.5 قواعد بنية الدليل المنظمة ✅ (تم فك الترميز والتنفيذ — مُنقّح 2026-07-02)

> **⚠️ تنقيح (2026-07-02): مخطط السجل المسطّح `61 01 00` أدناه كان به خطأ منهجي
> OFF-BY-ONE.** جسم المشهد هو TLV نظيف (§11.7)؛ **جسم عنصر قابل للرسم** (وسوم `0x30`/`0x38`
> ثابتة، `0x70` مؤشر) هو:
>
> ```
> 01 xx 00 [X u16][Y u16] …attrs… 61 [count u16][base u32][count×id u16] [05 05 00 01 pivX pivY]
> ```
>
> - السمة `0x01` تفتح الجسم: **X,Y = الزاوية العلوية اليسرى** على لوحة 466² (الـ `s16 x,y` في
>   `sty_picture_t` الخاص بـ SDK).
> - **جدول الإطارات `61 …` يغلق الجسم** (`base` = مؤشر الأصل؛ `count` 1 = صورة، 10/11 =
>   أطلس أرقام — "نوع السجل `0a/0b`" القديم كان في الواقع هذا العدد! — 7/13/2 = ورقة إطارات
>   التعقيد).
> - إضافات المؤشر: المصدر+المقياس `[src] 00 3c 00` داخل سمة `0x01`؛ نقطة الارتكاز في
>   **المقطورة** `05 05 00 01 [pivX][pivY]`. **مركز الدوران = `(X+pivX, Y+pivY)` لكل مؤشر** —
>   وليس نقطة ثابتة (233,233): توجد عقارب فرعية خارج المركز (مثلًا عقارب الاتصال 366 تدور حول 150,150).
>
> المسح الخطي لـ `61 01 00` كان يربط جدول الإطارات+نقطة الارتكاز للعنصر **N** بإحداثيات X/Y
> (وبايت الوسم، "f3" القديم) للعنصر **N+1** — بدا صحيحًا فقط على الاتصالات التناظرية التي
> تتشارك عقاربها المتجاورة هندسة شبه متطابقة. "جدار المتغير المضغوط" (المواصفة 24 §24.4.5) كان
> نفس هذا الفهم الخاطئ. تم تنفيذه كـ `scan_scene_drawables` في `core-rust/watchface_struct.rs`
> و`wfweb/src/codec/parse.ts` (المشهد = المصدر الأساسي للصور/المؤشرات؛ المسح المسطّح محفوظ
> للنصوص والبديل غير المغلف). تم التحقق منه عبر أوراكل `wfweb/compare.html` (العرض مقابل
> صور PNG الرسمية من المتجر، 99 اتصالًا): 64→72 جيد، 8→5 سيئ، متوسط الفرق 9.3→7.4%.

القراءة التاريخية للسجل المسطّح (مستبدلة، محفوظة للسياق):

- **صورة ثابتة** (`61 01 00`): `asset_ptr(u32) ‖ elemId(u16) ‖ 05 05 00 01 ‖ pivotX(u16) ‖
  pivotY(u16) ‖ 3B ‖ 01 ‖ 1b 00 ‖ X(u16) ‖ Y(u16)`. الزاوية العلوية اليسرى على لوحة 466² = `(X−pivotX, Y−pivotY)`.
- **مؤشر/عقرب** — نفس سجل الصورة، يُدار وقت التشغيل. **مركز الدوران = `(X+pivotX, Y+pivotY)`**
  (≈ 233,233 على الاتصالات التناظرية). **مصدر البيانات هو `u8` عند إزاحة السجل `+36`**، المقياس `u16` عند
  `+38` (=60): `0x0a`/`0x70` = الساعة (`h·30°+m·0.5°`)، `0x0e`/`0x71` = الدقيقة (`m·6°+s·0.1°`)،
  `0x12`/`0x72` = الثانية (`s·6°`). ✅ مؤكد عبر تفكيك أدوات الاسترجاع (البديل RTC 10:10:30).
- **أداة نص / رقم** (`61 0a 00`): `asset_ptr(u32) ‖ [10×u16 font metrics] ‖ 40 01 00 ‖ flag ‖
  3B ‖ 01 ‖ u16 ‖ X(u16) ‖ Y(u16)`. `asset_ptr` يشير إلى الصورة الرمزية "0"؛ الرقم *d* = الأصل عند
  `index("0") + d` (10 صور cf=5 متتالية، مثل `0123456789` وعلامات الترقيم `,°`). ✅ تم عرضه.
- **تعبئة التعقيد = فهرس الإطار** (✅ مؤكد لتعقيدات الأرقام/التعداد/المقياس، count>1): القيمة
  تفهرس **ورقة إطارات مُعدّة مسبقًا** في `.bin` — `frame = (count−1)·val/100` (نسبة مئوية) أو
  `frame = value` (رقم قلّاب / تعداد). جدول الإطارات = سجل فرعي `61 ‖ count(u16) ‖ base(u32) ‖
  count×id(u16)`. مثلًا ساعة 327 Digit Max الكبيرة هي ورقة من 13 إطارًا (الأرقام 0–12)، `frame = hour`.
- **حلقة التقدم / القوس = قص قطاع وقت التشغيل** (✅ 2026-07-02، **يصحح قراءة "الحلقات كأوراق إطارات"
  في المواصفة 25 §2**): وسم العنصر **`0x81`** يحمل **قرصًا كاملًا واحدًا** (`61` جدول إطارات
  `count == 1`)، والإسفين الجزئي هو ذلك القرص **مقصّصًا إلى قطاع دائري** (`frac = value/max`،
  باتجاه عقارب الساعة من الساعة 12) — تم التحقق منه بكسلًا بكسلًا على 322 Glare 2 وتأكيد `count==1` عبر
  **20 اتصالًا**. على القرص: جسم `0x81` = فرعي `0x01` (هندسة `x@+0 y@+2 w@+4 h@+6`، `61 1 base` مضمّن
  = القرص) + فرعي `0x5b` (مواصفات القوس). تم تنفيذه في wfweb (`blendSector`).
  ⚠️ السجل الفرعي `0x5b` **ليس** مجرد "`max` u16 `@+4`" كما وُثّق سابقًا — ذلك قرأ
  النصف المنخفض من `max i32` وفاته **زاويتا البداية/النهاية وعرض الخط** التي تقع
  بعده مباشرة. يوجد أيضًا شقيق إجرائي `0x80`/`0x5a` (مع نصف قطر صريح) لم يغطّه هذا
  المستند مطلقًا. تخطيط السجل الكامل، وما أخطأت فيه افتراضية "باتجاه عقارب الساعة من الساعة 12"
  السابقة: **§11.15**.
- **معرّف مصدر البيانات** — كتلة سمات `82` للعنصر تقع عند `delim+3` (بعد آخر `40 01 00`)،
  و**معرّف المصدر هو `u8` عند `+0x14`** (أيضًا `relX@+0x07 s16`، `relY@+0x09 s16`، `anchor@+0x0C/0E`،
  `mode@+0x15`، `frame-count@+0x1A`). المرساة < 0 = المحاذاة إلى حافة العنصر الأب. 🔎 البرنامج الثابت
  يحل المعرّف عبر جدول أدوات استرجاع من 142 إدخالًا عند `0x101f371c` (كل منها يستدعي `ux2sys_get(type)`).
  ⚠️ **يُفضَّل قراءة المعرّف كـ `meta[9]` من البنية (§11.11)** — حقل ثابت — بدلًا من هذا المسح
  الأمامي لسمة `82`، وهو مصدر خطأ off-by-one الموصوف في §11.8/§11.9. جدول المعرّفات الكامل في §16؛ لاحظ
  أن تسميات الصحة/الطقس التي أدرجها هذا القسم سابقًا سطريًا (`0x19` معدل ضربات القلب، `0x1b` البطارية،
  `0x24` درجة الحرارة، `0x36` الخطوات) **متنازع عليها وربما خاطئة** — انظر صندوق ⚠️ في §16.
  (مثال مجموعة `0x07:0x0b:0x0f` = HH:MM:SS أدناه غير متأثر.)
- **عقدة مجموعة** (`0x68`): تتداخل أطفالها داخل جسم TLV الخاص بها (`0x60` = قيمة/نص،
  `0x30` = ثابت)؛ كل `0x60` يحمل معرّف مصدره عند `data+16`. مثلًا مجموعة `0x07:0x0b:0x0f` =
  ساعة HH:MM:SS. محلل عناصر TLV = `0x100db55c` (جدول قفزات مفهرس بـ `tag−0x70`).

### 11.6 مصفوفة التأليف

| المسار | الحالة | ملاحظات |
|---|---|---|
| اتصال صورة من أي صورة | ✅ **منجز** | §11.3؛ تم التحقق منه على الجهاز |
| تثبيت أي من 103 اتصالات المتجر | ✅ **منجز** | §11.4؛ `9075`، `old_id`=النشط |
| إعادة تلوين خلفية cf=4 لاتصال متجر | ✅ **يعمل مباشرة** | استبدال الحمولة الكاملة في مكانها، ضبط `len` للأصل إلى **الحجم الجديد** للكتلة (≤ القديم)، الحفاظ على نفس بصمة الملف، تثبيت جديد |
| إعادة التأليف عبر القوالب (تبديل بكسلات أي طبقة + تحريك الهندسة) | ✅ **يُعرض عبر BLE** | الاتصال 373: الخلفية→سماوي + صورة cf=5→أحمر + تحريك X 224→100، كلها معروضة، العقارب حية |
| اتصال منظم اصطناعي 100% من الصفر | ✅ **الباني منجز، تم التحقق منه دون اتصال** | باني غلاف `0x20` في `watchface_struct.rs` (`build_container`/`serialize`/`validate_container`)؛ يعيد تدوير جميع الاتصالات الـ 103 بايتًا بايتًا + الاصطناعي يجتاز مدقق البرنامج الثابت (§11.7). 🟡 عرض على الجهاز عبر `9075` لم يُصوَّر بعد |
| خطوط النظام (`.font`) | ✅ **فك ترميز/عرض (الكل)** | LVGL bin (ليس ملكيًا)؛ 32 خط أرقام (`num*/nm*`، غير مضغوط) + 24 خط نص (`font*`، LVGL RLE `comp=1`) كلها تُفك ترميزها — 12208 حرفًا رسوميًا، 0 تجاوز، ASCII كامل. RLE = LVGL v8.3 `lv_font_fmt_txt.c` (حالات SINGLE/REPEATE/COUNTER الثلاث + مرشح XOR المسبق لكل صف)، منقول 1:1، بدون تفكيك |

⚠️ مزالق إعادة التلوين/إعادة التأليف التي تسبب شاشة سوداء أو `0a`: ترك **`len` الأصلي القديم** (الساعة
تقرأ ما بعد الكتلة → تجاوز → أسود)؛ **زيادة حجم الملف** (مرفوض عند التثبيت)؛ إعادة استخدام معرّف
**في مكانه** بدلًا من تثبيت جديد؛ **إعادة ترتيب الأصول** دون إصلاح سلسلة حجم كتلة الذيل المرجعية
(§11.11). ليس قاتلًا اليوم لكن اكتبه بشكل صحيح على أي حال: أي تغيير في المشهد أو مجموعة الأصول
يبطل كلمتي **CRC32** في الترويسة — أعد حساب كلتيهما (§11.4)، `@0x20` قبل `@0x00`.

### 11.7 غلاف المشهد `0x20` — تم فك ترميزه والباني منفذ ✅

اتصال **حقيقي** مُعاد تأليفه يُعرض لأنه يحافظ على غلاف مشهد الملف. جسم اصطناعي بحت من سجلات
`61 …` المسطحة **مرفوض** — محلل البرنامج الثابت (`WFManager_Parser`، `0xdb35c`)
يتطلب أن يبدأ الجسم (من الإزاحة `0x24`) بغلاف مشهد `0x20`. الملف الكامل هو:```
[0x00,0x24)  header:  perDialId@0 · version=1@4 · name[16]@8 · size_a@0x18 · size_b@0x1c · idWord0@0x20
[0x24, fa)   scene:   20 <u16 L0> ( 21 <u16 L1> ( 86 <len>=name , 30/70/80/81… drawables ) [ 22 … AOD ] )
[fa, EOF)    assets:  [dimsWord u32][len u32][payload = 1f 00 01 00 + LZ4] …
   size_a = filesize−36 · size_b = filesize−36−first_asset · 0x27+L0 == first_asset

المشهد هو TLV متداخل نظيف — [tag u8][len u16 LE][body]، حاويات الوسوم 0x20/0x21/0x22/0x68 تتكرر، والعناصر القابلة للرسم الطرفية 0x30 (ثابت) / 0x70 (عنصر/مؤشر) / 0x80 / 0x81 / 0x86 (اسم). (سجلات 61 01 00 / 61 0a 00 المسطحة هي أنماط تعيش داخل أجسام العناصر القابلة للرسم؛ المحلل القديم وجدها استدلاليًا — وقام بلصق الأجسام المتجاورة معًا، انظر مراجعة §11.5. تخطيط جسم العنصر القابل للرسم أصبح الآن مفكوكًا بالكامل هناك.) يجب أن يتناسب offset+len لكل طفل داخل نافذة والده؛ أول بايت في الجسم ≠ 0x20 → خطأ محلل −16؛ طفل يتجاوز نافذته → −2؛ أي منهما يجعل معالج 9065 (0xeb50c) يكتب إنهاء .

المنشئ مُنفَّذ ومُتحقق منه دون اتصال (core-rust/watchface_struct.rs: SceneNode / serialize / parse_scene / validate_container / build_container / build_container_raw؛ واجهة الأوامر cmfwatch-wfgen reframe):

  • scene_roundtrip_identity — جميع أقراص المتجر الـ103: parse_scene→serialize يعيد إنتاج المشهد بايتًا ببايت (قيم len المتداخلة المعاد حسابها تطابق) وvalidate_container ينجح على كل واحد منها.
  • build_reframe_identity / واجهة الأوامر reframe — إعادة تجميع ملف .bin بالكامل من الصفر يعيد إنتاج الملف بايتًا ببايت باستثناء بايت حشو اسم واحد (@0x17؛ ليس مجموع تحقق).
  • build_container_synthetic — يؤلف قرصًا جديدًا (خلفية + عنصر قابل للرسم متداخل في 20→21) يمر بالثابت الدقيق للبرنامج الثابت (build_container يُصدر نوافذ متداخلة صحيحة).
  • validate_rejects_bad_containers — يرفض جسم مسطحًا (→ ، خطأ التاريخي) وطفلًا يتجاوز نافذته (→ ).

🟡 لا يزال غير مُثبت (يحتاج الساعة، غير معطِّل): رفع عنصر اصطناعي من الصفر عبر 9075 ومشاهدته يُعرض — الإثبات الهيكلي دون اتصال يغطي بالفعل ما سبّب رفض 0a.

11.8 تحسينات دقة العرض (2026-07-02، القرص 275 "SlopeTime")

قارنت عرض wfweb مع الصور المصغرة الرسمية للمتجر (مرجع البكسل عبر جميع الأقراص الـ103) وسدّدت أربع فجوات:

  • X/Y للعنصر/المؤشر هما i16 (موقّعان). ✅ يمكن أن تكون نقاط الارتكاز سالبة للعناصر التي تمتد خارج اللوحة — مثل عقرب الثواني الأحمر في 275 عند Y = 0xFFFC = −4 (صورة 30×281، مصدر 0x12، مائلة من المركز فوق الحافة العلوية). قراءة X/Y كـ u16 (65532) جعلت الحارس يُسقطه. اقرأ كليهما كموقّعين واسمح بنطاق سالب صغير.
  • أرقام الساعة الرقمية يمكن أن تكون 0x60 img_numbers على المستوى الأعلى (ليس فقط داخل مجموعة 0x68)، و مصدر البيانات الحقيقي هو u8 عند إزاحة السجل −5 — فحص السمة الأمامي 82 هو منحرف ببايت واحد هنا بشكل منهجي ويلتقط سمة الأخ التالي (في 275 التقط رقم الدقيقة سمة يوم الأسبوع 0x18). "10:10" في 275 = الساعة 0x07@X≈306 + الدقيقة 0x0b@X≈369 مع : كعنصر ثابت مجاور بينهما، كل منهما أطلس 11 حرفًا (). ⚠️ عند تصحيح X/Y من ، يجب أن تتحرك ، أو يعيد التصدير إفساد تلك البايتات (يكسر نفس البصمة → ).

أيضًا: الصور المصغرة الرسمية للمتجر تُعرض عند 10:10 (وقت تسويقي كلاسيكي)، وليس 10:12 — مطابقة وقت المرجع إلى 10:10 تُقلل فرق البكسل المتوسط بشكل ملحوظ. محلل wfweb الآن يعيد تدوير جميع الأقراص الـ103 بايتًا دقيقًا (إصلاح إزاحة كتابة X/Y أعلاه أزال آخر عدم تطابق).

11.9 تخطي حاوية AOD + مصدر img_number المستقل (2026-07-03، القرص "Gradient")

  • افصل حاوية 0x22 الخاصة بـ AOD في عرضها الخاص. ✅ متجول المشهد يتخطى بالفعل 0x22، لكن فحص النص/الرقم المسطح اجتاز كامل [0x30, firstAsset) — لذا أصدر متغير التشغيل الدائم (AOD) لكل عنصر كطبقة عادية. على "Gradient" أطلس التاريخ الرمادي الخاص بـ AOD (الإزاحة في 0x22) رُسم فوق الأحمر العادي. الإصلاح: وسم كل سجل 0x22 بـ layer.aod=true (مع مجموعة إزالة تكرار خاصة به) ودع renderAt(…, aod) يعرضها فقط في وضع AOD (الوضع العادي يخفي طبقات aod؛ وضع AOD يخفي الطبقات العادية؛ الخلفية تُبدَّل بواسطة setAod وتُرسَم دائمًا). صافي ربح المرجع في الوضع العادي عبر المجموعة (284: 31%→21%، +18 أخرى) — متغيرات AOD كانت ترسم فوق العديد من الأقراص — ومفتاح AOD في المحرر الآن يعرض التخطيط الدائم الحقيقي بدلًا من العادي. AOD الحقيقي هو شاشة سوداء (لا يوجد مشهد خافت): إذا لم يكن للقرص إطار خلفية AOD مخصص (dial.aod)، يُخفى المشهد العادي في وضع AOD فيُعرض أسود + عناصر 0x22 بألوانها الخاصة. AOD تُحلل عبر متجول المشهد أيضًا (الآن يتكرر في حاوية موسومًا العناصر القابلة للرسم ، بدلًا من تركها للفحص المسطح حيث لم يطابق محور دورانها → "غير موضعة")؛ عقارب AOD تدور عند مركز اللوحة (أحيانًا تحمل x/y عقرب خارج المركز يتجاهله البرنامج الثابت — مثل ساعة Gradient عند ). المحرر يعرض هذا أيضًا كـ (§UI): كل شاشة تعرض طبقاتها فقط والتعديلات تُحفظ بشكل مستقل. عرض الوضع العادي متطابق بايتًا طوال الوقت؛ إعادة التدوير تبقى بايتًا دقيقة على جميع الأقراص الـ103.

11.10 عدد أرقام img_number — بايت 40 01 00 XX (✅ مؤكد من البرنامج الثابت)

كم عدد الأرقام التي يرسمها img_number هو بايت واحد في سجل الحقل — بايت البيانات XX من سجل السمة الفرعي 40 01 00 XX للعنصر (السجل الفرعي 0x40 الذي يجلس بعد جدول الإطارات 61 [count][base][glyph-ids]):

  • الربع المنخفض XX & 0x0F = عدد فتحات الأرقام (0 ⇒ افتراضي البرنامج الثابت 7).
  • البت 7 0x80 = حشو صفري (عرض أصفار بادئة، مثل "09" مقابل "9").

مؤكد بتفكيك البرنامج الثابت (صورة XIP 0x10000000؛ روتين العرض 0x100d8e60): NDIG = ldrb[40sub+3] & 0x0F (→7 إذا 0)؛ القيمة تُقيَّد value % 10^NDIG ويُرسَم بالضبط NDIG حروفًا MS أولًا، الأصفار البادئة مكبوتة ما لم يكن البت 7. u16 بعد المصدر (60 للتاريخ، 1000 للسعرات) ليس العدد — يغذي فقط إدراج حرف فاصل الآلاف/الملايين (cmp #1000/#1000000)، ولهذا لم يفعل تعديله شيئًا. معرّف المصدر لا يحد أيضًا.

مدرج تكراري للمجموعة عبر جميع حقول الأرقام الـ620 يطابق: حقول رقمين (ساعة/دقيقة/ثانية/تاريخ/درجة حرارة/نبض) تنتهي 40 01 00 02/0x82؛ السعرات …04؛ الخطوات …05؛ تقسيمات الساعة برقم واحد 0x81. لذا حقل التاريخ 40 01 00 82 = رقمين، بحشو صفري — هذا هو السبب الكامل وراء اقتطاع درجة حرارة فهرنهايت المعاد ربطها (≥100).

الإصلاح / المحرر: wfweb يحلل digitCount/digitZeroPad (+digitCountOff) لحقول الأرقام، يعرض "Digits" + "Zero-pad" في المفتش، يكتب البايت في مكانه (نفس البصمة)، والمعاينة تقيّد/تحشو إلى digitCount لتعكس البرنامج الثابت. لذا إعادة ربط مصدر حقل و ضبط عدد أرقامه يعمل لأي حقل (مثل التاريخ→درجة حرارة °F → Digits 3). مرجع الوضع العادي دون تغيير (0 تراجعات، 3 تحسينات صغيرة)؛ إعادة التدوير بايتًا دقيقة على جميع الأقراص الـ103. (فرضية "عرض الأرقام"/rectW السابقة كانت خاطئة — العرض للتخطيط فقط، وليس العدد.)

11.11 جرد العقد، سجل struct، وذيل مرجع الموارد ✅

المشهد (§11.7) هو TLV متداخل نظيف — [tag u8][len u16 LE][body]. جرد الوسوم الكامل كما لوحظ عبر المجموعة:

✅ هذا الجرد مكتمل للمجموعة. بالتكرار فقط في وسوم الحاويات أعلاه، TLV المشهد لجميع الأقراص الـ15 المفحوصة يمشي بالضبط إلى طول جذره المعلن مع صفر وسوم غير معروفة — لذا محلل يتعامل مع هذا الجدول يتعامل مع التنسيق بالكامل، والوسم غير المعروف يعني قراءة غير محاذاة، وليس نوع عقدة جديد. (احذر: متجول يتكرر في كل عقدة طولها يحدث أن يكون ≥ 3 سينزل إلى أجسام struct/0x5b ويخترع ذيلًا طويلًا من "وسوم" لمرة واحدة — أجسام الأوراق ليست TLV.)

💡 اختصار التأليف: يمكن تخطي التخطيط التلقائي 0x48/0x68 تمامًا — كل عنصر واجهة يمكن وضعه بإحداثيات x,y مطلقة مباشرة على المستوى الأعلى للشاشة، وهذا ما يفعله المنشئ من الصفر (§11.7). مطلوب فقط لقراءة الأقراص الموجودة. عرض meta بقيمة 0x8000 يعلّم struct كـ طفل تخطيط تلقائي لإطار (الموضع يأتي من الوالد، وليس من x,y).

جسم 0x01 struct — بادئة ثابتة من 18 بايت متبوعة بذيل مرجع موارد اختياري:``` +0x00 x i16 [signed — can be negative, see §11.8] +0x02 y i16 +0x04 meta[14] ──────────────────────────────────────────────────── meta[0..1] w u16 [0x8000 = auto-layout child of a frame] meta[2..3] h u16 meta[4..6] unknown [placeholder-looking (1,0,0)/(4,0,0); see §11.14] meta[7] accent-tint capability flag — 4 = tintable (§11.14) meta[9] DATA SOURCE ID (§16) meta[10] sub / variant meta[11..13] max u24 LE [the metric's nominal full-scale value] +0x12 ref tail [61 …] — absent on imageless rings (0x80/0x81, §11.15)

root@kitploit:~
> ✅ هذا يوحّد "الإزاحات السحرية" في الأقسام §11.5/§11.8/§11.9. تلك الأقسام تحدد الحقول *نسبيًا إلى
> البايت `0x61` لجدول الإطارات* — وهو ببساطة `+0x12` من هذا الهيكل، لذا `−18`/`−16` = `x`/`y` و
> **`−5` = `meta[9]`، معرف المصدر**. نفس البايتات، تخطيط واحد نظيف. الاستدلال الاستكشافي الأمامي لخاصية `82`
> الذي كان يخطئ بشكل منهجي بمقدار واحد ليس مطلوبًا على الإطلاق: اقرأ `meta[9]` من الهيكل.
> قيمة `max` لحقل رقمي هي أيضًا مجرد `meta[11..13]` (مثلًا، حقول يوم الشهر تحمل `max = 99`).

**ذيل المرجع** (`61`) — كيف تشير العقدة إلى خرائط البت الخاصة بها:```
+0x00  0x61      [tail type]
+0x01  count u16 [1 = single image · 10/11 = digit atlas · N = pick-list / frame sheet]
+0x03  base  u32 [ABSOLUTE FILE OFFSET of the first asset block]
+0x07  count × u16  = the BLOCK SIZE (8 + payload len) of each referenced asset, in order

⚠️ كانت تلك u16 الزائدة موثقة سابقًا على أنها "count×id(u16)" / معرّفات الحروف الرسومية. وهي أحجام كتل: base، بالإضافة إلى المجموع التراكمي لها، يجتاز مدخلات تجميع الأصول مدخلًا بمدخل (✅ تم التحقق بدقة لجميع المدخلات العشرة لأطلس الأرقام). نتيجتان:

  • يجب أن تكون الأصول المشار إليها بواسطة عقدة متتالية في تجميع الأصول. لا يوجد وصول عشوائي — السلسلة تتحرك للأمام فقط. خطط للتجميع بحيث تكون كل مجموعة أرقام (10)، وكل قائمة اختيار (N)، وكل ورقة إطارات كتلة واحدة متجاورة. الكاتب الذي يعيد ترتيب الأصول دون إصلاح السلسلة ينتج ملفًا يُقرأ كبيانات تالفة (→ شاشة سوداء).
  • فقط أول count−1 أحجام هي الحاملة للوزن؛ قيمة المدخل الأخير لا تُتبع أبدًا، لذا تحمل الملفات في البرية أحيانًا قيمة قديمة هناك. لا تعامل عدم التطابق في المدخل الأخير كمرجع مكسور.

0x28 معاينة — الصورة المصغرة للمتجر/الكتالوج مضمّنة في ملف .bin نفسه (27 عقدة معاينة عبر 15 قرصًا)، كبنية 0x08 pvStruct: بادئة من 5 بايتات بالإضافة إلى نفس ذيل المرجع، مع بدون x/y. مفيدة لبناء واجهة معرض دون شحن صور PNG منفصلة.

11.12 شروط الرؤية — الوسم 0x02 ✅

الأخ 0x02 لعنصر واجهة يجعلها شرطية. بدونها، يُرسم العنصر دائمًا. القواعد:``` count u8 , count × ( id u8 , op u8 , val u24 LE signed ) [5 bytes per entry]

root@kitploit:~
`id` هو معرّف مصدر بيانات (§16) — بما في ذلك **معرّفات الفتحات الاصطناعية** الواردة في §11.13. العوامل، مع
عدد مرات الظهور المُقاس عبر 15 قرصًا:

| op | المعنى | الظهور |
|---|---|---|
| `0x01` | ارسم إذا كانت `value == val` | 99 |
| `0x81` | نفس `0x01` (البت `0x80` مضبوط — يظهر في المتغيرات المتنافية حصريًا) | 48 |
| `0x02` | **إخفاء** إذا كانت `value == val` | 7 |
| `0x03` | ارسم إذا كانت `value == val`، حيث `val` هو **علامة عدم وجود بيانات** (مثل HR `1000`) | 13 |
| `0x05` | ارسم إذا كانت `value >= val` | 58 |
| `0x06` | ارسم إذا كانت `value <= val` | 50 |
| `0x04` | ⚠️ **غير معروف** — 15 ظهورًا، لا دلالات مؤكدة | 15 |

قاعدة الدمج (كما نفّذها العارض المرجعي، القناع `op & 0x7f`): إدخالات المساواة
تُدمج باستخدام **OR**، ثم يجب أن تتحقق **جميع** إدخالات الإخفاء/`>=`/`<=`.

هذه الآلية الواحدة تغطي معظم التباين الزمني للتنسيق، وتفسّر البنى التي تبدو
كعناصر واجهة مكررة:

- **تخطيطات 12 ساعة / 24 ساعة والوحدات المترية / الإمبراطورية** — مجموعتان من العناصر مكدستان في نفس الموضع، كل منهما مرتبطة
  بـ `id 0x73` (علامة الوحدات) مع `val` بقيمة 0 أو 1. يحتوي القرص 275 على ستة أزواج من هذا القبيل (12 عقدة).
- **عناصر نائبة لـ"لا توجد بيانات"** — العامل `0x03` مقابل قيمة حارسة، مثل `id 0x5f, val 1000` (القرص 275، مرتين):
  ارسم فن الشرطة الطويلة بدلاً من درجة الحرارة عندما يكون المقياس غير متاح.
- **تمييزات مجمّعة** — نطاق مقترن `0x05`/`0x06`، مثل سلسلة Metaball حيث يضيء كل رابط
  لنافذة مدتها 5 دقائق خاصة به.
- **بدائل فتحات التعقيد** — مرتبطة بمعرّفات الفتحات الاصطناعية الواردة في §11.13.

### 11.13 فتحات التعقيد القابلة للتكوين — `0x85` + `0x5f` ✅ (يحل محل §11.8)

خلص §11.8 إلى أن المقياس النشط لتعقيد قابل للتكوين هو حالة ذاكرة الجهاز ولا يمكن
استعادته من الملف. **كان ذلك خاطئًا** — كل من قائمة مقاييس الفتحة واختيارها الافتراضي
موجودان في ملف `.bin`. تحمل كل عقدة `0x85` شقيقًا `0x5f`:```
+0x00  slotIndex u8   [0-based position among sibling 0x85 nodes]
+0x01  count     u8   [how many metrics this slot offers]
+0x02  activeIdx u8   [index into the list below = the DEFAULT SHOWN METRIC]
+0x03  count × u8     [the metric ids themselves (§16)]           … NUL padding

البدائل التي تُرسم فعلياً هي مجموعات 0x68 عادية في مكان آخر من الشجرة، كلٌّ منها مُقيَّد بشرط 0x02 (§11.12) على المعرّف الاصطناعي 0x79 + slotIndex — لذا فإن متغيرات الفتحة 0 ترتبط بـ 0x79، وفتحة 1 بـ 0x7a، وهكذا. لعرض فتحة: اقرأ activeIdx، ثم ارسم المتغير الذي يطابق شرطه ذلك الفهرس.

مُقاس على أقراص حقيقية:

فتحتا القرص 275 بمقاييسهما الستة تمثلان 12 من عقد 0x02 الـ 26 لديه، تماماً كما توقعنا: 01 79 81 0X 00 00 و 01 7a 81 0X 00 00 لـ X = 0..5 — ستة بدائل مفتاحية على 0x79 (الفتحة 0) وستة على 0x7a (الفتحة 1). (البايتان 0x79/0x7a اللذان أسماهما §11.8 "بايت المثيل" هما معرّفا الربط هذان.) أما الـ 14 المتبقية فهي غير مرتبطة: 12 على 0x73 (علم 24 ساعة/وحدات المقاييس، val 0 أو 1 — ستة أزواج عناصر تتبدل بين تخطيطي 12 ساعة و24 ساعة) و2 على 0x5f مع op 0x03 و val = 1000، وهو العنصر النائب لغياب بيانات درجة الحرارة.

ما زال حالة جهاز حقيقية: أيّاً كان ما يختاره المستخدم لاحقاً في التطبيق المصاحب فإنه يتجاوز activeIdx وقت التشغيل، لذا فإن المعاينة تعيد إنتاج الافتراضي للملف، وليس بالضرورة ما يعرضه قرص معيّن. imgs[0] لعقدة 0x85 هو عنصر نائب "اضغط للتهيئة" يرسمه البرنامج الثابت فقط في وضع التحرير الخاص به — تخطَّه عند معاينة عرض الوقت العادي.

11.14 لون التمييز — علم القدرة meta[7] == 4 ✅

تتيح بعض الأقراص للمستخدم اختيار لون تمييز على الجهاز نفسه، ويستبدله البرنامج الثابت في الصور النقطية للعنصر وقت العرض. المفتاح هو بايت واحد: meta[7] من البنية (§11.11) — أي البايت +0x0B من جسم 0x01 — يساوي 4 يعلّم مورد(ات) ذلك العنصر كقابلة للتلوين.

  • إنه علم قدرة لكل عنصر، وليس لوناً. أعد تلوين كل بكسل غير شفاف من المورد المُعلَّم (اترك قناة alpha كما هي)؛ لا يوجد اختبار لون لكل بكسل.
  • الانتشار: 37 من 499 بنية و8 من 15 قرصاً مُقاساً هنا؛ مجموعة fmc تبلغ عن 56 من 100 قرصاً مع عنصر واحد مُعلَّم على الأقل.
  • 🛑 لا تدمج أبداً لون تمييز في البايتات المُصدَّرة. الاستبدال حيّ على الساعة؛ يجب أن يحتفظ ملف .bin المُسلَّم ببكسلاته الأصلية وإلا فقدت اختيار المستخدم نهائياً. طبّق الصبغة فقط في مسار المعاينة/اللوحة.

⚠️ لا "تحسّن" هذا إلى استدلال لوني — ذلك المسار طريق مسدود مثبت (موثّق من fmc بعد فعله بالطريقة الصعبة). النظرية البديهية هي أن البكسلات المُعلَّمة مدمجة بلون نائب يمكن التعرف عليه يستبدله البرنامج الثابت. لا يمكن أن ينجح: حلقة القرص 348 Tumbler القابلة للتلوين وشرائط الأرقام العادية غير القابلة للتلوين في القرصين 282 Radar Sweep / 291 Vertical تخبز نفس RGB (255,72,32) تماماً (مُفحَص بشكل شامل، كل بكسل)؛ والقرصان 305 Dots (عقرب الساعات) و306 Large Number (الأرقام) قابلان للتلوين بينما يُخبزان أبيض خالص، لذا فإن اختبار اللون سيفوتهما تماماً. التحسينات المتتالية (1 → 4 ألوان مرجعية، بالإضافة إلى قائمة سماح لأدوار العناصر) كلها فشلت. اقرأ العلم.

مُتحقق منه مقابل الجهاز الحقيقي / التطبيق المصاحب على 7 أقراص، مُختارة لاختبار الاتجاهين — 349 Theatre و376 Digits time و305 Dots و306 Large Number و304 Elaborate 2 كلها تقدم إعداد التمييز وكلها تحتوي عناصر meta[7]==4؛ و316 Trailing (عقرب محمر، بلا إعداد) و312 Disc و295 Vortex لا تقدم أي شيء ولديها صفر عناصر مُعلَّمة.

meta[4..6] يقع بجوار العلم مباشرة ويبدو وكأنه قد يرمّز لوناً في بعض البنيات (RGB يبدو حقيقياً مع ذيل f1=1,f2=255، مقابل العنصر النائب (1,0,0)/(4,0,0) في البنيات المُعلَّمة). إنه لا يرتبط بقدرة التمييز. ⚠️ غير محلول؛ تجاهله.

11.15 حلقات التقدم — 0x80/0x5a (إجرائية) و 0x81/0x5b (مقصوصة بالصورة) ✅

كلا النوعين من الحلقات يقرن بنية قصيرة (x، y، meta مع معرّف المصدر — عادةً بدون ذيل ref إطلاقاً) بأخٍ يحدد القوس. §11.5 وثّق فقط "0x5b: max u16 @+4"، وهو النصف المنخفض من max i32 ويفوته هندسة المسح. السجل الكامل:``` +0x00 min i32 LE [always 0 in the corpus] +0x04 max i32 LE [100 in the corpus, except dial 332 = 60] +0x08 start i16 LE [sweep start, units of 0.1° — SIGNED] +0x0a end i16 LE [sweep end, units of 0.1° — SIGNED] +0x0c width u16 LE [stroke width in px] +0x0e radius u16 LE [0x5a ONLY — 0x81 takes its radius from the clipped image] +0x0e / +0x10 trailer 01 00 kk ⚠️ unresolved (see below)

root@kitploit:~
الاقتران صارم: `0x80` يحمل دائمًا `0x01` + **19 بايت** من `0x5a`، و`0x81` يحمل دائمًا `0x01` + **17 بايت** من `0x5b` (✅ 26/26 حلقة عبر 15 قرصًا). ⚠️ لكن **`0x81` المقتطع بالصورة يهيمن** — 25 من تلك الـ 26. المتغير الإجرائي `0x80`/`0x5a` ظهر **مرة واحدة** (القرص 273)، لذا فإن حقل `radius` الخاص به وتخطيط الـ 19 بايت يعتمدان على عينة واحدة؛ تعامل معه بحذر حتى يُرى مرة أخرى.

`frac = clamp((value − min) / (max − min), 0, 1)`، والقوس المملوء يمتد من `start` نحو `end`. الزاوية صفر عند **الساعة 3**، موجبة في اتجاه عقارب الساعة. أمثلة مُقاسة:

| القرص | الوسم | min..max | start ← end | العرض | نصف القطر |
|---|---|---|---|---|---|
| 273 Activity Mood | `0x5a` | 0..100 | **−102.8° ← 102.8°** | 42 | 222 |
| 273 Activity Mood | `0x5b` | 0..100 | 270.0° ← 90.0° | 80 | (صورة) |
| 276 Dichotomy | `0x5b` | 0..100 | 60.0° ← −120.0° | 23 | (صورة) |
| 304 Elaborate 2 | `0x5b` | 0..100 | −2.0° ← 358.0° | 24 / 80 | (صورة) |
| 366 Combo | `0x5b` | 0..100 | 0.0° ← 270.0° | 18 | (صورة) |
| 368 Function | `0x5b` | 0..100 | 0.0° ← 360.0° | 20 | (صورة) |

> ⚠️ **هذا يصحح افتراض "في اتجاه عقارب الساعة من الساعة 12"** في §11.5. ذلك هو فقط الحالة الخاصة
> `start = 0, end = 3600` (مسح كامل، حيث يكون الاصطلاح غير قابل للملاحظة). الأقراص الحقيقية تستخدم
> **مقاييس جزئية** (مروحة ±102.8° للقرص 273، وحلقة ثلاثة أرباع 270° للقرص 366) و**مسحات سالبة**
> (60° ← −120° للقرص 276)، لذا فإن أي عارض يمسح دائمًا دائرة كاملة من الأعلى يرسم تلك بشكل خاطئ.
> 🟡 الاصطلاح الدقيق لزاوية الصفر وقاعدة الاتجاه يأتيان من عارض fmc، تم التحقق منهما بالتقاطع
> مع هذه القيم على القرص — لم يتم التحقق منهما بكسلًا-بكسلًا على الجهاز بواسطة هذا المستودع.
> لاحظ أن `frac = value/max` ونتيجة قص القطاع `0x81` في §11.5 **تم التحقق منهما** (القرص 322).

⚠️ **غير محلول: الذيل ثلاثي البايت `01 00 kk`.** يأخذ `kk` قيمًا تبدو معقولة (104، 152، 216،
232، 248) والفرضية الواضحة هي نصف قطر — **تم اختبارها ودحضها**: القرص 366 يستخدم `kk = 104`
لكل من حلقة 82×82 وحلقة 166×166، والقرص 273 يستخدم `kk = 232` لكل من حلقة 440×440 وحلقة 284×284.
ليس نصف قطر، وليس قطرًا. ربما شفافية/نمط. أعد إرساله كما هو حرفيًا.

🟡 **مأزق مُبلَّغ عنه، غير مُتحقق منه هنا:** في عارض fmc، رقم `0x60` الذي يساوي معرف مصدره معرف
**أي** حلقة على نفس الشاشة يعرض `round(frac × 100)` بدلاً من القيمة الخام — لذا
رقم معدل ضربات القلب بجوار حلقة معدل ضربات القلب يعرض `36` بدلاً من `71 bpm`. الحل البديل الموثق
لديهم هو وضع الحلقة والرقم على **معرفين مستعارين** لنفس المقياس (الخطوات
`0x19`/`0x26`/`0x49`، السعرات `0x1c`/`0x1e`/`0x48`). ما إذا كان هذا سلوكًا للبرنامج الثابت أم خاصًا
بعارضهم **غير مثبت** — يستحق فحصًا مباشرًا قبل التصميم حوله.

---

## 12. تفاصيل النقل الجماعي وOTA

جدول النقل موجود في §6. نقاط إضافية مؤكدة:

- **AGPS/EPO** ✅: أول كتلة مكتوبة تبدأ بالترويسة ASCII `000000010000…`. الحلقة الكاملة
  init ← `[A05F ↔ 905F]×N` ← finish لوحظت على السلك (~892 كتلة).
- **تحديث البرنامج الثابت OTA** (`9040`–`9042`، finish `9041`) 🔎: البنية مُخططة؛ حمولة INIT2 = بايتات الإصدار
  (مثل `0b 00 00 39` = 11.0.0.57). **لم يُختبر ميدانيًا** (التطبيق يعطل تحديث البرنامج الثابت هنا). صور
  البرنامج الثابت تبدو **غير موقعة — التكامل هو CRC32 فقط** (لا توجد توقيعات غير متماثلة لوحظت في الهندسة العكسية).
- ⚠️ لأن OTA و`FACTORY_RESET (009A 0001)` يتشاركان الجلسة المصادق عليها، فإن مصادقة BLE صالحة واحدة
  كافية لمسح أو (من حيث المبدأ) تعطيل الساعة. تعامل بحذر.

---

## 13. المستشعرات

✅ الأجهزة المكشوفة عبر BLE:

- **PPG بصري** — معدل ضربات القلب (يدوي/تلقائي/تمرين/راحة)، SpO₂، والإجهاد المشتق من HRV.
- **مقياس تسارع ثلاثي المحاور** — الخطوات، المسافة، السعرات، مراحل النوم، رفع المعصم، الإيقاع.
- **GNSS/GPS** (بمساعدة AGPS) — مسار التمرين (`WORKOUT_GPS`) ودفع الموقع (`GPS_PUSH`).

لا يوجد **بارومتر/مقياس ارتفاع، بوصلة، جيروسكوب، أو مستشعر حرارة الجلد/الجسم**. يوجد
ثيرمستور NTC داخلي (حرارة اللوحة/البطارية) لكنه قابل للقراءة **فقط** عبر قناة AT
(`AT GETNTCTEMP`، §14) — تدفق سجل حرارة الجلد `0155` فارغ على هذا الطراز.

**اختطاف عناصر البيانات** (لا توجد واجهة برمجة تطبيقات حقيقية للتعقيدات/ربط البيانات — انظر §11.5): يمكن
إعادة استخدام حقول النص الموجودة في الساعة لعرض بيانات خارجية سريعة. مُثبت ✅: سلسلة **المدينة**
للطقس (`WEATHER_SET_1`، مثل `"BRA 2x1 ARG"` ظهرت على العنصر) وحقول **المسار/الفنان**
للموسيقى؛ قائمة **جهات الاتصال** (20 × name[32]+number[25]) تعمل كلوحة بيانات قابلة للتمرير.
كلها دفعات، وليست تعقيدات دائمة.

---

## 14. قناة AT للمصنع / الصدفة (`77d4ff01` / `77d4ff02`)

قناة أوامر AT نصية عادية منفصلة، مستقلة عن البروتوكول المؤطر. ✅ مُختبرة مباشرة:

- **قراءة:** `AT GETSECRET` (سر الاقتران 16 بايت)، `GETVERSION`، `GETSN`، `GETNAME`، `GETPID`،
  `GETBATLV` (mV خام، مثل `3853mv`)، `GETGSENSOR` (تسارع خام بوحدة g، `X=… Y=… Z=…`)،
  `GETNTCTEMP` (°C، NTC داخلي).
- **كتابة / تشغيل:** `AT SETMOTOR=1` (اهتزاز المحرك)، `SETHR/SETHRV/SETSPO2=…` (حقن اختبار
  المستشعر)، `SETLCDSWITCH/SETGPSSWITCH/SETKEYSWITCH`.

الردود تنتهي بـ `,OK`. أوامر `SET*` تنفذ عمومًا لكنها قد لا تعيد صدى `,OK` عبر BLE — تأكد
حالة بحالة.

---

## 15. الميزات المقيدة بالبرنامج الثابت / غير المتاحة (🔎 هندسة عكسية للبرنامج الثابت)

بعض الميزات موجودة في البرنامج الثابت لكنها معطلة حسب الطراز/المنطقة و**لا يمكن الوصول إليها من
الهاتف/BLE** — تحتاج تعديلًا على البرنامج الثابت، وهو خارج النطاق هنا:

- **صوت ChatGPT** — البوابة = معرف ميزة `ux2sys` `0x9e`، مُغذى من NVRAM/EFUSE/المنطقة عند الإقلاع؛ على
  هذا الطراز علم الدعم `908b = 00`. غير قابل للتأثير من الهاتف أو الحساب أو BLE (مؤكد بالتجربة
  + الهندسة العكسية). التطبيق مجرد مرحّل؛ الصوت يذهب من الهاتف ← سحابة Nothing.
- **ضغط الدم** — نظام فرعي كامل موجود في البرنامج الثابت، مُعطل حسب الطراز/المنطقة.
- **Alipay / دفع NFC** — واجهة مستخدم كاملة موجودة، لطراز الصين فقط.
- **غائب في الأجهزة/البرنامج الثابت:** ECG، SOS/طوارئ، NFC عام.

---

## 16. معرفات مصدر البيانات / التعقيدات

ليست ضرورية لبناء عميل BLE، لكنها **أساسية لتأليف أو عرض قرص**: هذه هي قيمة
`meta[9]` (§11.11) التي تربط عنصرًا بالبيانات الحية، و`id` شرط الرؤية (§11.12)، و
إدخالات قائمة مقاييس الفتحة (§11.13). البرنامج الثابت يحلها عبر **جدول توزيع getter بـ 142 إدخالًا**
عند `0x101f371c`، كل إدخال يستدعي `ux2sys_get(type)` (🔎 هندسة عكسية للبرنامج الثابت).

### الوقت / التاريخ — ✅ راسخ جيدًا

| id | المعنى | id | المعنى |
|---|---|---|---|
| `0x01` | الساعة (12/24 ساعة حسب إعداد الجهاز) | `0x0f`, `0x12` | الثانية (ناعمة) |
| `0x04` | الساعة (24 ساعة) | `0x10`, `0x11` | عشرات / آحاد الثانية |
| `0x07` | الساعة (24 ساعة إجباري) | `0x71`, `0x72` | الثانية (نابضة / زاوية العقرب) |
| `0x02`, `0x03` | عشرات / آحاد الساعة-12 | `0x13` | علم AM/PM (0 = صباحًا، 1 = مساءً) |
| `0x05`, `0x06`, `0x08`, `0x09` | عشرات / آحاد الساعة | `0x15`, `0x16` | الشهر |
| `0x0a`, `0x70` | زاوية عقرب الساعة | `0x17` | يوم الشهر |
| `0x0b` | الدقيقة | `0x18` | يوم الأسبوع (0 = الاثنين ⚠️) |
| `0x0c`, `0x0d` | عشرات / آحاد الدقيقة | `0x0e`, `0x71` | زاوية عقرب الدقيقة |

معرفات العشرات/الآحاد ترسم **رقمًا واحدًا** — عنصر مرتبط بأحدها يعرض حرفًا واحدًا، وليس القيمة
الكاملة (القرص 284 Square). معرفات زاوية العقرب (§11.5) هي `0x0a`/`0x70` للساعة = `h·30° + m·0.5°`،
`0x0e`/`0x71` للدقيقة = `m·6° + s·0.1°`، `0x12`/`0x72` للثانية = `s·6°` (✅ مؤكد بفك تجميع
الـ getters، احتياط RTC 10:10:30).

### الصحة / المستشعرات / الطقس — ⚠️ متنازع عليه، اقرأ عمود الأدلة

| id | المعنى (أفضل قراءة حاليًا) | الدليل |
|---|---|---|
| `0x19` | **الخطوات** | في قائمة فتحات 368 بجانب `0x1a`؛ يطابق `parse.ts` لهذا المستودع |
| `0x1a` | **معدل ضربات القلب** | في قائمة فتحات 368 بجانب `0x5f` و`0x19` |
| `0x1c` | السعرات | قائمة الفتحات، أيقونة اللهب في التطبيق المصاحب |
| `0x1e` | السعرات (اسم مستعار) | المجموعة |
| `0x22` / `0x23` | المسافة كم / ميل (الجزء الصحيح) | المجموعة |
| `0x74` / `0x75` | المسافة كم / ميل (الجزء الكسري) | المجموعة |
| `0x76` | المسافة (نموذج الفتحة) | قائمة الفتحات، أيقونة الطريق |
| `0x24` | **نسبة البطارية %** | قائمة الفتحات، أيقونة البرق |
| `0x30` | نسبة البطارية % | المجموعة |
| `0x36` / `0x5f` | درجة الحرارة | `0x5f` = قائمة الفتحات، أيقونة سحابة-شمس؛ 361 TempoG يربط رقمًا عاديًا |
| `0x48` | الوقوف (ساعات الوقوف) | قائمة الفتحات، أيقونة شخص واقف |
| `0x8b` | AQI | قائمة الفتحات |
| `0x73` | علم 24 ساعة / وحدات مترية | المجموعة |
| `0x25`–`0x27`, `0x49`, `0x6c`, `0x6f` | نسبة الهدف % / أسماء مستعارة للفتحات للخطوات والسعرات | المجموعة |
| `0x6a` | ⚠️ مقياس فتحة غير محدد | يظهر في 4 قوائم فتحات |
| `0x79 + slotIndex` | **اصطناعي** — ليس مقياسًا؛ معرف اختيار الفتحة (§11.13) | ✅ §11.13 |

> ⚠️ **ثلاثة جداول في هذا المستودع اختلفت؛ هذا الجدول هو التوفيق.** مراجعات سابقة من §16
> و§11.5 قرأت `0x19` كمعدل ضربات القلب، و`0x1b` كبطارية، و`0x24` كدرجة حرارة و`0x36` كخطوات —
> و`wfweb/src/codec/mock.ts` لا يزال يرمز تلك القراءة، بينما `wfweb/src/codec/parse.ts` يرمز
> قراءة مختلفة (`0x19` خطوات، `0x24` نسبة هدف %، `0x48` وقوف، `0x1a` طقس). الجدول أعلاه يتبع
> القراءة الأفضل دليلًا (التسميات معايرة بواسطة fmc ضد أيقونات قائمة فتحات عناصر التطبيق المصاحب،
> انظر §Sources). **الكود لم يتغير بعد — `mock.ts` و`parse.ts` لا يزالان غير متسقين
> مع بعضهما البعض ومع هذا الجدول.** تعامل مع تسميات معرفات الصحة كـ ⚠️ حتى يربط شخص ما
> حقلًا بكل معرف ويقرأ الساعة.
>
> أقوى دليل فردي هو **قائمة فتحات القرص 368 Function** (§11.13)، التي تقدم
> `0x5f 0x1c 0x19 0x48 0x24 0x76 0x1a 0x8b` كـ **ثمانية مقاييس مميزة قابلة للاختيار من قبل المستخدم** في قائمة واحدة.
> مهما كانت التسميات، لا يمكن أن يكون اثنان من تلك الثمانية نفس المقياس — مما يستبعد
> `0x19` = `0x1a` = معدل ضربات القلب و`0x24` = `0x5f` = درجة الحرارة في نفس الوقت.

التعقيدات الحلقية/القوسية مع **ورقة إطارات** تفهرس إطارًا مُصيَّرًا مسبقًا (مثل 50 % = إطار 50 من 100)
مدمجًا في `.bin` الذي ترسله (§11.5)، لذا لا حاجة لحزمة RES خارجية؛ الحلقات بدون صور تُرسم
من مواصفة القوس بدلاً من ذلك (§11.15).

---

### البطاقات السريعة (بلاطات الرئيسية) — `QUICK_CARD (906D)` ✅

بلاطات الرئيسية للساعة. الهاتف يختار فقط **أي** البلاطات تظهر و**بأي ترتيب** — البلاطات
تُعرض بواسطة البرنامج الثابت (لا توجد قناة محتوى). أول بايت من الحمولة = أمر فرعي: `00` = GET، `01` =
SET؛ **كلاهما يستخدم `0x906D`** (`0x906C` مذكور لكنه غير مستخدم — الاستعلام عنه ينتهي بوقت انتهاء). الرد = `A06D`.

> 🛑 إرسال `assemblyId` مختلق **يمسح شاشات الساعة** (يقبل القائمة، لا يمكنه مطابقة
> المعرفات، لا يعرض شيئًا). أرسل فقط المعرفات التي **قرأتها** عبر GET؛ استعد عبر التطبيق الرسمي أو
> إعادة ضبط المصنع.

**رد GET** ✅: `status(1) ‖ 00 ‖ N(1) ‖ N × group`، group = `tag=01 ‖ K(1) ‖ K×(assemblyId, sportId)`.
إطار حقيقي: `01 00 04  01 02 5d00 6100  01 03 1900 2e00 2300  01 03 5c00 0400 5a02  01 03 4800 5100 5300`
= 4 شاشات / 11 بطاقة (`5a02` = بطاقة الرياضة، sportId 2).

**الفتحات:** كل شاشة بها **4 فتحات**. نوع البطاقة يحدد حجمها — `circular`/`square` = فتحة واحدة،
`rectangle` = فتحتان. التحقق هو حساب فتحات بسيط (Σ ≤ 4 لكل شاشة)؛ لا توجد بطاقات متنافية.
`sportId` هو `0` باستثناء بطاقات الرياضة (87–91). المعرفات `64` و`95` غير موجودة.

**كتالوج `assemblyId`** (كل نوع منطقي = نطاق متصل من 6 متغيرات نمط `_0`..`_5`؛
`0` = فتحة فارغة):

| عشري | البطاقة | عشري | البطاقة |
|----|----|----|----|
| 0 | فتحة فارغة | 49–53,97–98 | الطقس |
| 1–6 | الخطوات | 54–58 | المؤقت |
| 7–12 | السعرات | 59–62 | التنفس |
| 13–18 | الوقوف | 63,65–67 | ساعة الإيقاف |
| 19–24 | نشاط معتدل | 68–71 | البطارية |
| 25–30 | معدل ضربات القلب | 72–76 | الأخيرة |
| 31–36 | SpO₂ | 77–81 | جهات الاتصال |
| 37–42 | الإجهاد | 82–86 | القرص / الهاتف |
| 43–48 | النوم | 87–91 | الرياضة (`sportId` ≠ 0) |
| 92 | الموسيقى | 93/94/96 | سجل النشاط / PAI / الدورة |

---

## الملحق أ. كتالوج الأقراص المخزنة (id ← الاسم)

§11 يشير إلى الأقراص بالمعرف الرقمي في كل مكان (275 SlopeTime، 322 Glare 2، 357 Silhouette، …). المعرفات
`273`–`376` هي أوجه المتجر/المخزون؛ المعرف هو ما يبلغ عنه `DIAL_COMMAND (9055/a055)` كنشط و
ما يأخذه `9075` كـ `old_id` (§11.4). 100 من أصل 103 معرفات معروفة مُسمّاة أدناه؛ الباقية هي
أكواد ROM (§11.4). الأسماء والتجميع كما يعرضها التطبيق المصاحب الرسمي.

- **الافتراضي** (6) — `273` Activity Mood · `274` Sun Circle · `275` SlopeTime · `276` Dichotomy · `277` Prismatic Time · `280` Multifunction
- **التناظري** (34) — `286` Sundial · `287` Simple Dial · `292` City · `294` Sudoku · `305` Dots · `306` Large Number · `309` Gradient · `310` Glare · `311` Bold · `313` Classical · `314` Fragment · `315` Infinite · `316` Trailing · `322` Glare 2 · `326` Chrono Master · `327` Digit Max · `328` Coherent · `329` Wheel · `330` Zenith · `331` Intersection · `335` Time Phase · `336` Energetic · `338` Chronos · `341` Dual View · `346` Time Windmill · `347` Large Panel · `349` Theatre · `352` Elegant Sweep · `360` Explorer · `364` SportPulse · `370` Hemisphere · `371` ActiveTrio · `372` Time Wheel · `373` Traditional Pointer
- **الرقمي** (41) — `281` Metaball · `282` Radar Sweep · `283` Radio · `285` Widgets · `288` Type · `289` Rotate · `290` Gradual · `291` Vertical · `293` Stairs · `296` Ladder · `297` Ray · `298` Eclectic · `299` Echo · `300` Mono Dial · `301` Orbit · `302` Calendar · `303` Space · `307` Sprung · `308` Sundial 2 · `319` One Line · `320` Orienteer · `321` Revolution · `323` Dash · `324` Finesse · `325` Metric · `333` Circularity · `334` Globe of Time · `337` Time Finder · `339` Suprematism · `340` Sport Mode · `345` Time Dot · `350` Timeline · `351` Cyclopes · `353` Dual phase · `357` Silhouette · `359` Ring data · `361` TempoG · `362` Steady · `365` Elegance · `369` Solar System · `376` Digits time
- **متعدد الوظائف** (10) — `304` Elaborate 2 · `344` InfoMeter · `348` Tumbler · `354` Dual · `363` Vintage · `366` Combo · `367` Complex Figure · `368` Function · `374` Cirquary · `375` InfoHub
- **الإبداعي** (8) — `284` Square · `312` Disc · `317` Disc 2 · `318` Dominos · `332` Flux · `342` Perfect Match · `343` Progress Day · `358` Asteroid
- **Diwali** (1) — `295` Vortex

---

## المصادر

تم إعادة بناء تخطيطات البايت أعلاه من البرنامج الثابت (1.0.0.73)، وAPK الرسمي (3.5.7)، و
التقاطات حية مفككة التشفير ضد جهاز حقيقي. التنفيذ المرجعي لهذا المشروع موجود في
`core-rust/src/{commands,frame,crypto,health,session}.rs` (Rust)، ومحرر TypeScript `wfweb/`، و
أدوات Python `cmftool/` (`pair.py`، `session.py`، `wf_codec.py`، `upload_custom.py`، …).

**عمل مستقل مدمج هنا.** [freethinkel/fmc](https://github.com/freethinkel/fmc) — محرر
أوجه ساعات SvelteKit + سوق لنفس الساعة — قام بشكل مستقل بالهندسة العكسية
لتنسيق `.bin` من مجموعة ~100 وجه ووصل إلى عدة نتائج كانت هذه الوثيقة تفتقدها أو كانت
خاطئة فيها. مستنداتهم `docs/cmf-protocol.md`، و`src/lib/modules/editor/lib/{wf,render}.ts` و
`src/lib/modules/device/lib/ble.ts` تستحق القراءة مباشرة. النتائج المعتمدة، كل منها أُعيد التحقق
منه ضد بايتات الأقراص قبل كتابته هنا:

| النتيجة | المكان | الحالة هنا |
|---|---|---|
| كلمتا الترويسة كلتاهما CRC32 (متغير غير قياسي) | §11.4 | ✅ أُعيد التحقق 9/9 أقراص؛ **تصحيح** "لا يوجد مجموع اختباري حاجب" |
| شروط الرؤية، الوسم `0x02` | §11.12 | ✅ أُعيد التحقق؛ كان غير موثق |
| قائمة مقاييس الفتحة + `activeIdx` الافتراضي، `0x79 + slotIndex` | §11.13 | ✅ أُعيد التحقق على 275/368/273/304؛ **يحل محل** §11.8 |
| علم قدرة الصبغة المميزة `meta[7] == 4` | §11.14 | ✅ أُعيد التحقق من الانتشار؛ كان غير موثق |
| مواصفة القوس الكاملة (زوايا المسح، العرض، نصف القطر) | §11.15 | ✅ أُعيد التحقق؛ **تصحيح** "`max` u16 `@+4`" |
| `u16` ذيل المرجع هي أحجام كتل، وليست معرفات حروف | §11.11 | ✅ أُعيد التحقق بدقة على أطلس 10 أحرف |
| تذييل 36 بايت؛ متغير البايت السحري `0x02`؛ `0x86` = 64 ب؛ معاينة مدمجة `0x28` | §11.4، §11.11 | ✅ أُعيد التحقق |
| تسميات معرفات الصحة/الطقس معايرة ضد قائمة فتحات التطبيق المصاحب | §16 | ⚠️ معتمد كأفضل قراءة؛ التعارضات مُعلَّمة |
| UUID خدمة الصدفة هو `77d4e67c-…`، ونطاق `optionalServices` في Web Bluetooth | §1 | ⚠️ تقرير وحدة واحدة، لم يُعد التحقق هنا |
| مشاركة معرف الرقم مع الحلقة تعرض نسبة مئوية | §11.15 | 🟡 مُبلَّغ عنه، **غير** مُتحقق منه هنا |
| كتالوج معرف القرص المخزن ← الاسم | الملحق أ | ✅ معتمد كما هو |
تنزيل الأداة
الغرضالخدمةالخاصيةالخصائص
كتابة الأوامر0000fff0-0000-1000-8000-00805f9b34fb0000fff2-…كتابة
إشعار الأوامر0000fff0-…0000fff1-…إشعار
كتابة الصدفة (AT)—77d4ff01-2fe2-2334-0d35-9ccd078f529cكتابة
إشعار الصدفة (AT)—77d4ff02-…إشعار
كتابة البيانات الجماعية—02f00000-0000-0000-0000-00000000ffe1كتابة
إشعار البيانات الجماعية—02f00000-…ffe2إشعار
الاسمcmd1,cmd2
TIMEFFFF 8004
FIRMWARE_VERSION_GET / _RETFFFF 8006 / FFFF 0006
SERIAL_NUMBER_GET / _RET00DE 0002 / 00DE 0001
BATTERY005C 0001
TRIGGER_SYNC005C 0002
USER_INFO_SET / _RET 🔎✅0095 0001 / 0095 0003
FACTORY_RESET009A 0001
DEVICE_REBOOT 🔎FFFF 9080
RESOLUTION_GET 🔎 (→ 466×360)FFFF 907F
GPS_PUSH / _RETFFFF 906A / FFFF A06A
UNBIND_SET / _RETFFFF 907A / FFFF A07A
الاسمcmd1,cmd2
AUTH_PHONE_NAMEFFFF 8049
AUTH_WATCH_MACFFFF 0049
AUTH_PAIR_REQUEST / _REPLYFFFF 8047 / FFFF 0048
AUTH_NONCE_REQUEST / _REPLYFFFF 804B / FFFF 004C
AUTHENTICATED_CONFIRM_REQUEST / _REPLYFFFF 804D / FFFF 0004
AUTH_FAILEDFFFF A061
الاسمcmd1,cmd2
APP_NOTIFICATION0065 0001
INCOMING_CALL ⚠️0064 0001
CALL_REMINDER_REQUEST / _RESPONSEFFFF 9066 / FFFF A066
FIND_PHONE005B 0001
FIND_WATCH005D 0001
FIND_WATCH_TOGGLEFFFF 9069
SMS_MESSAGE_PUSH / _RETFFFF 906E / FFFF A06E
QUICK_REPLY_SET / _RETFFFF 9073 / FFFF A073
الاسمcmd1,cmd2
ALARMS_SET / _GET0063 0001 / 0063 0002
CONTACTS_SET / _GET00D5 0001 / 00D5 0002
STANDING_REMINDER_SET / _GET0060 0001 / 0060 0002
WATER_REMINDER_SET / _GET0061 0001 / 0061 0002
TASK_REMINDER_SET / _RET ⚠️FFFF 9072 / FFFF A072
الاسمcmd1,cmd2
GOALS_SET / _ACK005E 0001 / 005E 0003
UNIT_LENGTH / _ACKFFFF 9067 / FFFF A067
UNIT_TEMPERATURE / _ACKFFFF 9068 / FFFF A068
TIME_FORMAT / _ACK005F 0001 / 005F 0003
WAKE_ON_WRIST_RAISE / _GET / _ACK0062 0001 / 0062 0002 / 0062 0003
LANGUAGE_SET / _RETFFFF 9058 / FFFF A06B
HEART_MONITORING_ENABLED_SET / _GET009B 0001 / 009B 0002
HEART_MONITORING_ALERTSFFFF 9059
DO_NOT_DISTURB / _GET0099 0001 / 0099 0002
SPORTS_SET / _GET00DC 0001 / 00DC 0002
SPORT_LINKAGE_SET / _RETFFFF 9076 / FFFF A076
SPORT_DATA_SYNC 🔎 (معدل ضربات القلب/سعرات/خطوات مباشر)FFFF 9078 / FFFF A078
FEMALE_CYCLE_SET / _RETFFFF 9071 / FFFF A071
SLEEP_CONFIG_SET / _RET (الحد الأدنى المستهدف)FFFF 9074 / FFFF A074
WORLD_CLOCK_GETFFFF 906F
WORLD_CLOCK_DST_SET / _RETFFFF 9083 / FFFF A083
VITALITY_GET / _RETFFFF 9079 / FFFF A079
VITALITY_SW_SET / _RETFFFF 9070 / FFFF A070
الاسمcmd1,cmd2
DIAL_COMMAND_SET / _RET (قائمة/إعادة ترتيب/تحديد)FFFF 9055 / FFFF A055
DIAL_CONFIG_SET / _RETFFFF 9075 / FFFF A075
CHANGE_DIAL (⚠️ خامل على 1.0.0.73 — لا تستخدمه)009F 0001
QUICK_CARD_SET/GET / _RET (كلاهما على 906D)FFFF 906D / FFFF A06D
الاسمcmd1,cmd2
ACTIVITY_FETCH_1 / _2FFFF 8005 / FFFF 9057
ACTIVITY_FETCH_ACK_1 / _2FFFF 0005 / FFFF A057
ACTIVITY_DATA0056 0001
SLEEP_DATA / _GET0058 0001 / 0058 0002
SPO20055 0001
STRESS009D 0001
HEART_RATE_MANUAL_AUTO0053 0001
HEART_RATE_RESTING00DA 0001
HEART_RATE_WORKOUT00E0 0001
SKIN_TEMP_HISTORY 🔎 (فارغ على هذا SKU)0155 0001 / 0155 0002
WORKOUT_SUMMARY / _V30057 0001 / 0160 0001
WORKOUT_GPSFFFF A05A
المجالINIT1 طلب/ردINIT2 طلب/ردCHUNK طلب/كتابةFINISH ack1/ack2
واجهة الساعة (صورة)8052/00529063/A063A064/9064A065/9065
واجهة الساعة (منظمة/تبديل)8052/00529075/A075A064/9064A065/9065
البرنامج الثابت9052/A0529040/A040A042/9042A041/9041
AGPS/EPO905E/A05E—A05F/905FA060/9060
الإزاحةالحجمالحقل
04الطابع الزمني (ثواني epoch)
44الخطوات
84المسافة (م)
124السعرات الحرارية
1616محجوز (لوحظ 0)
الإزاحةالحجمالحقل
04بداية الجلسة (epoch، UTC)
44الاستيقاظ (epoch، UTC)
82إجمالي_العميق_ث
102إجمالي_الأساسي_ث
122إجمالي_REM_ث
142إجمالي_الاستيقاظ_ث
162⚠️ [غير مؤكد] (معرف الجلسة/الدرجة؟ القيم المرصودة لا تطابق مجموع السجلات)
ac 49 1f 01
  • CONTACTS_SET (00D5 0001) ✅: N × 57 بايت = name(32) ‖ phone(25). تعرض واجهة الساعة حتى 20.
  • ALARMS_SET (0063 0001) ✅ — تصحيح لـ Gadgetbridge (الذي وضع التسمية في النهاية، مبطنة بـ 0xff — خطأ). 40 بايت لكل منبه، big-endian: secondsOfDay(i32) ‖ index(u8) ‖ enabled(u8) ‖ repetition-bitmask(u8) ‖ flag(u8) ‖ label[32] UTF-8. التسمية عند الإزاحة 8 وتظهر على الساعة. repetition = قناع بتات أيام الأسبوع (0 = لمرة واحدة)؛ flag ⚠️ [غير مؤكد] (علامة لمرة واحدة؟). مثال (13:30، idx 2): 0000bdd8 02 01 15 00 "Alarm…".
  • GOALS_SET (005E 0001) ✅ — يستخدم التطبيق الرسمي والتنفيذ المرجعي 10 بايت، big-endian DailyTargetBean v1: steps(u32 BE) ‖ distance_m(u32 BE) ‖ calories_kcal(u16 BE). (هذه هي صيغة Gadgetbridge؛ التقارير السابقة بأن الساعة "تجاهلتها" كانت خطأ فك تشفير جلسة قديمة، وليست مشكلة حمولة.) 🔎 هندسة البرنامج الثابت العكسية تُظهر أيضًا متغيرًا ممتدًا 29 بايت أطول (يضيف sleep_min/exercise_min/stand_h + 6 أعلام تمكين، كلها u32 BE بعد بادئة flag(u16 LE)، مع نطاقات مفروضة: خطوات 2000–30000، مسافة 1000–99000، سعرات 100–5000، نوم 360–720، تمرين 30–90، وقوف 6–16) — ليس المسار الافتراضي للتطبيق؛ فضّل صيغة 10 بايت إلا إذا كنت بحاجة إلى الأهداف الإضافية.
  • STANDING_REMINDER / WATER_REMINDER (0060/0061 0001) ✅: 11 بايت: enabled(1) ‖ threshold_min(u16 LE) ‖ dndStart(u32 LE) ‖ dndEnd(u32 LE). لاحظ أن "النافذة النشطة 08:00–22:00" المعروضة في الواجهة هي إعداد افتراضي ثابت في البرنامج الثابت وليست محمولة في الحمولة.
  • SPORTS_SET (00DC 0001) ✅: count(1) = 36 خانة ‖ activityTypeCode[36] (أكواد نشطة ثم حشو 00). يحدد الرياضات التي تظهر في قائمة التمارين بالساعة.
  • HEART_MONITORING_ENABLED (009B 0001) ✅: بايت kind — 01 = HR 24/7، 02 = SpO₂، 04 = الإجهاد (يُقاس كل 30 دقيقة).
  • HEART_MONITORING_ALERTS (FFFF 9059) ✅: معطل = 00؛ مفعل = 01 ‖ hrLow ‖ hrHigh ‖ sportHrHigh ‖ spo2Low ‖ 00 00 00 00 (حد 0/255 = "بدون حد").
  • FEMALE_CYCLE (FFFF 9071) ✅: 01 ‖ predictionOpen ‖ notifySwitch ‖ cycleStartSwitch ‖ cycleStartNotifyBefore ‖ ovulationStartSwitch ‖ ovulationStartNotifyBefore ‖ fertileStartSwitch ‖ fertileStartNotifyBefore ‖ period(1) ‖ cyclePeriod(1) ‖ cycleStartDate(u32) ‖ markStart(u32) ‖ markEnd(u32) (مُلتقط: period=5، cyclePeriod=0x1c=28).
  • QUICK_REPLY (FFFF 9073) ✅: TLV — count(1) ‖ total(1) ‖ [id(1) ‖ len(u16 LE) ‖ msg-UTF8]… (7 ردود افتراضية مُلتقطة ومفكوكة التشفير).
  • WORLD_CLOCK (FFFF 906F) ✅: يرسل معرفات مدن رقمية، وليس أسماء (01 ‖ count ‖ cityId(2 BE)…)؛ تعيّن الساعة المعرفات من جدول داخلي. إعداد DST FFFF 9083 = count ‖ [id(u16 LE) ‖ dst(u16 LE) ‖ start(u32 LE) ‖ end(u32 LE)]….
  • MUSIC_INFO_SET (FFFF 905C، 131 ب) ✅: state(1: 0=لا شيء/1=إيقاف مؤقت/2=تشغيل) ‖ volume(1) ‖ volumeMax(1) ‖ track(64) ‖ artist(64). ترسل الساعة أيضًا MUSIC_BUTTON (A05D) مرة أخرى.
  • WEATHER_SET_1 (FFFF 906B، 199 ب) ✅ — استخدم هذا: 7×9 بايت أيام + 24×2 بايت ساعات + city(32) + 7×8 بايت شروق/غروب (LE). درجات الحرارة مشفرة كـ (temp_c + 100) & 0xFF. ⚠️ نفس الحمولة المرسلة على WEATHER_SET_2 (0066 0001) لا تحدّث أداة الطقس على Pro 2 — استخدم دائمًا 906B. (سلسلة المدينة أيضًا ناقل اختطاف بيانات مثبت — انظر §13.)
  • FIND_WATCH (005D 0001) ✅: الحمولة 0x01 → ترن الساعة/تهتز (+ ACK 005D 0003).
  • GPS_PUSH (FFFF 906A) ✅ — big-endian، خط الطول أولاً: 16 بايت ts(u32 BE) ‖ lon×1e7(i32 BE) ‖ lat×1e7(i32 BE) ‖ 00 00. تم التحقق من موقع حقيقي.
  • WORKOUT_GPS (FFFF A05A) ✅ — little-endian، خط الطول أولاً: 12 بايت ts(i32) ‖ lon×1e7(i32) ‖ lat×1e7(i32).
  • TIME (FFFF 8004): انظر §7.
  • cfbppالبيانات النقطية (بعد LZ4)الاستخدام
    42RGB565-LEخلفية معتمة (FULL/THUMB)
    53RGB565-LE (2 بايت) + ألفا (1 بايت) لكل بكسلصور نقطية مضادة للتعرج (الحروف، العقارب، الأيقونات)
    13 (0x0d)0.5قناع ألفا 4-بت؛ البرنامج الثابت يلوّنه وقت التشغيلأطلس أرقام الأطلس
    24 (0x18)4RGBA8888طبقات كاملة الألوان (بما في ذلك aodImage الدائم التشغيل)
    1—JPEG/JFIF (ff d8 ff)، استخرجه بأي مفكك ترميزإطارات رسوم متحركة نادرة
    0a
    0x61
    NotEnvelope
    0a
    ChildOverflow
    61 0a 00
    −18/−16
    إزاحات الكتابة أيضًا
    0a
  • فتحات التعقيد متعددة المتغيرات — المقياس النشط ليس في ملف .bin ⚠️ هذه النقطة كانت خاطئة وتم استبدالها بـ §11.13. التعقيد القابل للتهيئة يُؤلف كـ N عقد مجموعة 0x68 مكدسة عند نفس (x,y)، كل واحدة تُرسم فقط عندما يطابق شرط رؤية — هذا الجزء كان صحيحًا. لكن قائمة المقاييس لكل فتحة (قائمة 0x1c/0x6a/0x48/0x24/0x19/0x76 في 275) هي بالضبط قائمة المقاييس، وليست "معرّفات خيار/نمط"؛ مؤشر النشط الافتراضي هو بايت في الملف؛ و البايت 0x79/0x7a ليس "بايت مثيل" بل المعرّف الذي تُربط عليه البدائل (0x79 + slotIndex). معاينة ثابتة يمكنها إعادة إنتاج الافتراضي في الملف. انظر §11.13.
  • التعقيدات غير النشطة المرساة بالحافة (مثل نص bpm عند (446,0)، شوهد على 275/302/325/365/375) هي فتحات لا يرسمها البرنامج الثابت في العرض الافتراضي — قيمتها لا يمكن حتى أن تتسع قبل حافة اللوحة. عالجها كمخفية في المعاينة.
  • عقارب
    0x22
    aod
    0x22
    @69,209
    واجهة تحرير معزولة Normal|AOD
  • 0x60 img_number مستقل (cnt=10) — المصدر عند −5، منحرف ببايت واحد للأمام. ✅ نفس الانحراف كما في §11.8 لكن لأرقام غير الساعة: تاريخ "Gradient" كان عند (203,80) أعلى المنتصف مع مصدر 0x17، لكن الفحص الأمامي 82 التقط مؤشر الجار getter الزاوية (0x0a) و موضع المؤشر → الرقم عُرض في موضع المؤشر بمصدر زائف. الإصلاح: لـ 61 0a 00 img_number في غلاف 0x60، ثق بـ −5/−18/−16 عندما يكون المصدر الأمامي مستحيلًا لرقم (مصدر-0 أو getter زاوية مؤشر 0x0a/0e/12/70/71/72) و يكون موضع −18/−16 صالحًا وغير صفري (حارس غير الصفر يتخطى أرقام أطفال المجموعة مع relX=0).
  • 0x17 = التاريخ (يوم الشهر)، 0x24 = درجة الحرارة — متميزان. القرص 340 يستخدم كليهما (0x17 "Jun 09" و0x24 درجة حرارة منفصلة)، لذا 0x17 هو التاريخ، وليس درجة الحرارة. القرص الذي تعرض ساعته درجة حرارة في فتحة 0x17 هو تعقيد مكوَّن من المستخدم (حالة جهاز)، وليس افتراضي الملف.
  • الوسمالدورحاوية؟الجسم
    0x20جذر المشهد (غلاف الجسم، ليس عنصرًا قابلًا للرسم)✅أطفال
    0x21الشاشة العادية✅أطفال
    0x22شاشة AOD (§11.9)✅أطفال
    0x28صورة مصغرة معاينة الكتالوج المضمّنة✅طفل 0x08 واحد
    0x68مجموعة / حاوية تخطيط تلقائي✅إطار 0x48 + أطفال
    0x30صورة ثابتة، أو اختيار بالقيمة من N صور✅0x01 (+0x02)
    0x60قراءة رقمية حية (شريط أرقام)✅0x01 + 0x40 (+0x02)
    0x70عقرب دوار✅0x01 + محور 0x05
    0x80حلقة تقدم، إجرائية✅0x01 + 0x5a (§11.15)
    0x81حلقة تقدم، مقصوصة بالصورة✅0x01 + 0x5b (§11.15)
    0x85فتحة تعقيد قابلة للتعيين من المستخدم✅0x01 + 0x5f (§11.13)
    0x01struct — هندسة + سمات (أدناه)—x,y,meta[14] + ذيل مرجع
    0x02شرط رؤية (§11.12)—قائمة شروط
    0x05محور — flag u8, pivotX u16, pivotY u16—5 بايت
    0x08pvStruct — prefix[5] + ذيل مرجع، بدون x/y (معاينة فقط)——
    0x40عدد الأرقام / علم الحشو الصفري (§11.10)—1 بايت
    0x48إطار — x,y,w,h,gap,align صف/عمود تخطيط تلقائي——
    0x5a / 0x5bمواصفات قوس لـ 0x80 / 0x81 (§11.15)—19 بايت / 17 بايت
    0x5fقائمة مقاييس الفتحة لـ 0x85 (§11.13)——
    0x86عقدة اسم العرض، دائمًا 64 بايت بالضبط، منتهية بـ NUL، غير مرسومة—64 بايت
    dialslotcountactiveIdxmetric ids→ active
    275 SlopeTime0601c 6a 48 24 19 760x1c calories
    275 SlopeTime1641c 6a 48 24 19 760x19 steps
    368 Function0805f 1c 19 48 24 76 1a 8b0x5f temperature
    368 Function1865f 1c 19 48 24 76 1a 8b0x1a heart rate
    273 Activity Mood0401c 24 48 6a0x1c calories
    304 Elaborate 20/1401c 48 6a 24 / 24 1c 6a 480x1c / 0x24
    dialslotcountactiveIdxmetric ids→ active
    275 SlopeTime0601c 6a 48 24 19 760x1c calories
    275 SlopeTime1641c 6a 48 24 19 760x19 steps
    368 Function0805f 1c 19 48 24 76 1a 8b0x5f temperature
    368 Function1865f 1c 19 48 24 76 1a 8b0x1a heart rate
    273 Activity Mood0401c 24 48 6a0x1c calories
    304 Elaborate 20/1401c 48 6a 24 / 24 1c 6a 480x1c / 0x24