ما هو محرر الملفات الثنائية؟

محرر الملفات الثنائية هو أداة تتيح تعديل الملف مباشرة بصيغة الست عشري (Hex) أو بصيغة ASCII. بينما يتعامل محرر النصوص التقليدي مع «السلاسل النصية» كوحدة التحرير، يمنحك المحرر الثنائي إمكانية الوصول إلى تسلسل البايت الخام الذي يتكون منه الملف لتعديل أي بت أو بايت في أي موضع تشاء.

تعد هذه الأدوات ضرورية في تحليل البرمجيات، واستعادة البيانات، والتحقيق في البروتوكولات. العديد من التطبيقات المكتبية مثل HxD أو Binary Ninja معروفة في هذا المجال، لكن هدفي هذه المرة كان بناء محرر ثنائي يعمل بالكامل داخل المتصفح ومن ملف واحد.


منهجية التنفيذ

  • يعمل على الواجهة الأمامية بالكامل (من دون أي إرسال للشبكة)
  • استخدام واجهة FileReader لقراءة الملفات المحلية
  • عرض الست عشري (Hex) في الجهة اليسرى وASCII في الجهة اليمنى
  • الدخول إلى وضع التحرير بالنقر المباشر مع إمكانية الكتابة فوق القيم
  • تمييز الخلايا المعدلة باللون الأحمر
  • التبديل بين وضعي التحرير Hex وASCII
  • السماح بتنزيل النتيجة المعدلة كملف مرة أخرى

العقبات التي واجهتني وكيف حللتها

1. انحراف إطار التحرير

في الإصدار الأول كان إطار التحديد الأصفر الذي يحيط بالخلية النشطة يبدو مائلاً قليلاً، والسبب هو line-height في CSS وترتيب العناصر عبر flex. المحاذاة الرأسية لحرف الخط والمحاذاة الخاصة بالإطار لم تتطابق. حتى مع استخدام خط ثابت العرض، قد تختلف خطوط الأساس حسب المتصفح والخط المستخدم، فكان الإطار يميل لأعلى أو لأسفل.

أجبرتُ الخاصية vertical-align: middle، وحولت الخلية إلى inline-block لتتطابق الارتفاعات، فاختفت المشكلة.

.hex-cell.editing {
  outline: 2px solid yellow;
  vertical-align: middle;
}

2. ظهور حروف عند الضغط على مفتاح ESC

في بداية التطوير كنت أضغط مفتاح ESC أثناء تفعيل الخلية فيُكتب فيها “EE” أو غيرها من الرموز.

كان السبب أنني لم ألتقط حدث keydown، فكان المتصفح يمرر قيمة المفتاح مباشرة إلى منطق الإدخال. كانت خوارزمية الإدخال تتوقع أرقاماً من 0 إلى 9 أو الحروف A-F فقط، لكن مفتاح Escape له شفرة أيضاً (keyCode = 27 في الكود القديم، وkey = "Escape" في الواجهات الحديثة). ومن دون حارس يوقفه، فُسرت القيمة على أنها الحرف "E"، فظهر “EE” في الخلية.

الحل بسيط: اعتراض Escape في معالج keydown، وإلغاء التحرير، ومنع السلوك الافتراضي.

document.addEventListener("keydown", e => {
  if (e.key === "Escape") {
    cancelEdit();
    e.preventDefault(); // منع تدفق المفتاح إلى مسار الإدخال العام
  }
});

3. بقاء اللون الأحمر بعد إعادة التحميل

بعد قراءة ملف وتعديله ثم فتح ملف آخر، كانت العلامات الحمراء السابقة تبقى معروضة.

السبب أن المصفوفة أو الحالة التي تتبع الخلايا المعدلة لم تُصفَّر عند قراءة الملف الجديد. حتى لو أُعيد تهيئة منطقة العرض، بقيت أعلام “التغيير” فعّالة، فتنتقل الحالة إلى الملف التالي.

أضفتُ نداءً إلى clearModifiedState() مباشرة بعد تحميل الملف لإعادة ضبط الأعلام بالكامل.

4. التبديل بين وضع Hex وASCII

في البداية كان الوضعان Hex وASCII قابلين للتحرير في الوقت نفسه، ما خلق ارتباكاً حول أيهما يعكس الحقيقة. لأن بايتاً واحداً يُدار عبر مسارين مختلفين، نشأ تنافس على الكتابة في الخلية نفسها. فعند إدخال قيمة ASCII كان العرض الست عشري يتغير فوراً، لكن أثناء الإدخال المؤقت كانت تظهر حالات غير متسقة.

لتفادي ذلك، حوّلت الاختيار إلى قائمة منسدلة تحدد الوضع الحالي، وجعلت الإعداد الافتراضي هو التحرير الست عشري. هكذا يفهم المستخدم بوضوح «أنا أحرر Hex الآن»، كما أصبح منطق التزامن أبسط.


جزء من الشيفرة

نفذت معالجة التحرير الفعلية بالشكل التالي:

function applyEdit(offset, newValue) {
  if (mode === "hex") {
    buffer[offset] = parseInt(newValue, 16);
  } else {
    buffer[offset] = newValue.charCodeAt(0);
  }
  markModified(offset);
  render();
}

الدالة markModified(offset) مسؤولة عن تمييز الخلايا بالأحمر عبر إضافة فئة CSS عندما تختلف القيمة عن الأصل. التنفيذ بسيط ومباشر.


الخلاصة

أصبح محرر الملفات الثنائية في المتصفح أداة تعمل من دون اتصال بالإنترنت وتضمن الأمان لأنها تعمل كلياً على جانب العميل.

  • عندما تحتاج إلى تعديل بسيط على ملف ثنائي
  • عند التحقيق في بيئة لا يمكن تثبيت برامج فيها
  • في التعليم والتدريب لفهم «تسلسلات البايت» بشكل بصري

يمكن الاستفادة من الأداة في هذه السيناريوهات وغيرها.