Я не верю в теорию заговора производителей (электроники) о сокращении ресурса изделия, просто выполняется поиск оптимального соотношения цены / качества для определенного сегмента рынка. Но есть один момент, от которого у меня дико подгорает чуть ниже живота. Это производители принтеров. Мало того, что цены на расходники такие, что оправдать их может только наличие в составе крови младенца, рожденного девственницей, но и повсеместное «чипирование» картриджей, перешедшее уже в не самые бюджетные лазерные агрегаты. Но реально «бомбануло» меня от следующего изделия: RICOH SP 150SUw. Данный девайс был приобретен по причине переезда из Подмосковья в Минск и необходимостью копирования / печати большого количества документов для получения разрешения на работу / проживание.

Выбор был основан на следующих требованиях: МФУ не дорогой, компактный, сетевая печать, наличие драйверов под Linux (Fedora / Mint / OpenSUSE). Герой статьи обладал всеми этими характеристиками, но один нюанс был не замечен. Кривой счетчик ресурса. Если интересно, как вылечить и

(Памятка для граммар-наци от человека с легкой степенью дисграфии)
Да, у меня есть проблемы с естественными языками. Я это осознаю. И предупреждаю / предполагаю, что в тексте допущено достаточно много орфографических и пунктуационных ошибок. Надеюсь, нет логических и технических. Это не желание возрождать «олбанию», просто так получается. Не могу позволить личного корректора, а написание статей мне не приносит прибыли, только отнимает время, но хочется поделиться идеями/мыслями с сообществом. На ошибки указывать не надо, не буду исправлять, так как к моменту исправления, с последующим вычитыванием, статья уже уйдет в глубины ленты Хабра и работа будет напрасной. Покорнейше прошу отнестись с пониманием, а если Вас передергивает от неправильно поставленной запятой, лучше не открывайте пост. Пожалейте свои и мои нервы, иначе —

добро пожаловать!

Дело в том,что МФУ считает не количество израсходованного тонера, а количество отпечатанных страниц. Если стартовый картридж я отпечатал с достаточно большой степенью заполнения, то со вторым случился казус. Дело в том, что жена достаточно быстро влилась в местное сообщество англомам и часть работы, касающаяся обеспечения полиграфией, легла на нас. Так вот, всякие там контурные картинки для вырезания / раскрашивания / оригами заполняют тонером, от силы, 1% листа. В результате у меня оказался «пустой» по счетчику, но фактически — полный процентов на 70 картридж. Нет, я не настолько беден, чтоб не позволить себе купить новый, не на столько экологически воспитан, чтоб испытывать моральные мучения, выкидывая исправное изделие на свалку, но от чувства, что тебя законно развели — кресло начало дымиться, что и было порывом к действию.

Честно, не надеялся стать «мамкиным хакером», учитывая что подобную аппаратную защиту очень сложно сломать. Рассчитывал, как минимум, на приличные алгоритмы шифрования и OTP память (однократно программируемая). Но реальность оказалась куда банальней. К счастью, по запросу обнуления картриджа, было много инструкций, а «защитным чипом» оказалась вполне себе распространенная I2C EEPROM AT24C01, распаянная с минимальным «обвесом» на плату. По большому счету, на следующей картинке, можно закончить статью:


Оригинальное видео

Любым программатором читаем содержимое микросхемы, «зануляем» ячейки, обведенные красными рамками и изменяем пару последних цифр серийного номера. Следует обратить внимание, что серийник — текстовая строка, заканчивающаяся пробелом, так что менять надо в диапазоне 0...9 (0x30...0x39). Физический адрес микросхемы, распаянной на плате — 0x03. Но… Встречайте сапожника без сапог. Нет универсального программатора, по этому берем PIC16F819 и PICKit 3, не, для продвижения в массы — Arduino UNO / Nano, пару резисторов на 4.7k (от 3k до 10k для этой задачи — сойдет), метр МГТФа или Вашего любимого провода и собираем следующую «схему»:



В сборе это смотрится так:



Плату «защиты» я снял с картриджа, чтобы удостовериться, что приведенная в мануале распиновка соответствует реальности и получения физического адреса микросхемы на шине:


Контактные площадки, слева направо: GND, +5V, SCL. SDA.

Можно не мудрить с изготовлением переходника, а подпаять провода непосредственно на плату, не снимая ее с картриджа. Далее — копируем мой говнокод в среду Arduino IDE:

Говнокод
#include <stdint.h>
#include <Wire.h>

//----------------------------------------------------------------
#define EERROM_SZ         (0x80)
#define EERROM_PHY_ADDR   (0x03)
#define EERROM_HEAD       (0x50)
#define PRN_BUFF_SZ       (0x08)
#define SER_START_ADDR    (0x20)
#define SER_END_ADDR      (0x2B)
#define SER_MOD_ADDR0     (0x2A)
#define SER_MOD_ADDR1     (0x29)
#define SER_MOD_ADDR2     (0x28)

//----------------------------------------------------------------
static uint8_t eeprom_data[EERROM_SZ];
static bool erased;
static bool z_filled;
//----------------------------------------------------------------
static uint8_t ee_read(uint8_t phy_addr, uint8_t addr)
{
  uint8_t res;

  Wire.beginTransmission(EERROM_HEAD | phy_addr);
  Wire.write(addr);
  Wire.endTransmission();
  Wire.requestFrom(EERROM_HEAD | phy_addr, 1);
  res = Wire.read();
  
  return res;
}

//----------------------------------------------------------------
static void ee_write(uint8_t phy_addr, uint8_t addr, uint8_t data)
{
  Wire.beginTransmission(EERROM_HEAD | phy_addr);
  Wire.write(addr);
  Wire.write(data);
  Wire.endTransmission();
  delay(5);
}


//----------------------------------------------------------------
static void read_data(uint8_t phy_addr)
{
  uint8_t addr;
  uint8_t data;
  
  erased = true;
  z_filled = true;
  
  Serial.print("Read from phy addr ");
  Serial.print(phy_addr);

  for (addr = 0; addr < EERROM_SZ; addr++)
  {
    if (0 == (addr & 0x03))
    {
      Serial.print(".");
    }
    data = ee_read(phy_addr, addr);
    eeprom_data[addr] = data;

    if (0xFF != data)
    {
      erased = false;
    }

    if (0x00 != data)
    {
      z_filled = false;
    }
  }
  
  Serial.println("Ok");
}

//----------------------------------------------------------------
static void write_data(uint8_t phy_addr)
{
  uint8_t addr;

  Serial.print("Write to phy addr ");
  Serial.print(phy_addr);

  for (addr = 0; addr < EERROM_SZ; addr++)
  {
    if (0 == (addr & 0x03))
    {
      Serial.print(".");
    }
    ee_write(phy_addr, addr, eeprom_data[addr]);
  }

  Serial.println("Ok");
}

//----------------------------------------------------------------
static bool check_data(uint8_t phy_addr)
{
  uint8_t addr;
  uint8_t data;
  
  Serial.print("Check from phy addr ");
  Serial.print(phy_addr);

  for (addr = 0; addr < EERROM_SZ; addr++)
  {
    if (0 == (addr & 0x03))
    {
      Serial.print(".");
    }
    data = ee_read(phy_addr, addr);
    if (eeprom_data[addr] != data)
    {
      Serial.println("FAILED");
      return false;
    }
  }
  
  Serial.println("Ok");
  return true;
}


//----------------------------------------------------------------
static void print_data(void)
{
  uint16_t addr;
  char prn_buff[PRN_BUFF_SZ];
  
  for(addr = 0; addr < EERROM_SZ; addr++)
  {

    if (0x00 == (addr & 0x0F))
    {
      snprintf(prn_buff, PRN_BUFF_SZ, "%4X:  ", addr);
      Serial.print(prn_buff);
    }
    
    snprintf(prn_buff, PRN_BUFF_SZ, "%2X ", eeprom_data[addr]);
    Serial.print(prn_buff);
    
    if (0x0F == (addr & 0x0F))
    {
      Serial.print("\n\r");
    }
  }
  Serial.print("\n\r");
}

//----------------------------------------------------------------
static void prn_serial(void)
{
  Serial.print("Serial #: ");
  Serial.write(&eeprom_data[SER_START_ADDR], 1 + SER_END_ADDR - SER_START_ADDR);
  Serial.print("\n\r");
}

//----------------------------------------------------------------
static void mod_serial(void)
{
  eeprom_data[SER_MOD_ADDR0]++;
  if (eeprom_data[SER_MOD_ADDR0] > '9')
  {
    eeprom_data[SER_MOD_ADDR0] = '2';
  }

  eeprom_data[SER_MOD_ADDR1]++;
  if (eeprom_data[SER_MOD_ADDR1] > '9')
  {
    eeprom_data[SER_MOD_ADDR1] = '3';
    eeprom_data[SER_MOD_ADDR2]++;
    if (eeprom_data[SER_MOD_ADDR2] > '9')
    {
      eeprom_data[SER_MOD_ADDR2] = '1';
    }
  }
}

//----------------------------------------------------------------
static void reset_mileage(void)
{
  uint8_t i;
  
  for (i = 0x12; i <= 0x1F; i++)
  {
    eeprom_data[i] = 0;
  }

  for (i = 0x2C; i <= 0x7F; i++)
  {
    eeprom_data[i] = 0;
  }
}

//----------------------------------------------------------------
static bool test_magic(void)
{
  if (0x32 != eeprom_data[0]) return false;
  if (0x00 != eeprom_data[1]) return false;
  if (0x01 != eeprom_data[2]) return false;
  if (0x03 != eeprom_data[3]) return false;
  return true;
}

//----------------------------------------------------------------
void setup()
{
  int key;

  Serial.begin(9600);
  Wire.begin();

  Serial.println("\tSP 150 cartridge mileage resetter");
  Serial.println("Connect like this:");
  Serial.println("             TOP");
  Serial.println("______________________________");
  Serial.println("|o |GND| |+5V| |SCL| |SDA|  <=");
  Serial.println("|  |GND| | 5V| | A5| | A4|    ");
  Serial.println("------------------------------");
  Serial.println("        cartridge roller");
  
  Serial.println("\n\r\n\r\tTo start, press 'm' or any button for test (not prog)...\n\r");

  do
  {
    key = Serial.read();
  }
  while(-1 == key);
  
#if 0
  for (uint8_t paddr = 0; paddr < 8; paddr++)
  {
    Serial.print("Scan phy ");
    Serial.println(paddr);
    for (uint8_t i = 0; i < 5; i++)
    {
      Serial.print("Read from ");
      Serial.print(i);
      Serial.print(".........");
      Serial.println(ee_read(paddr, i));
    }
  }
  return;
#endif

  read_data(EERROM_PHY_ADDR);
  Serial.println("Read:");
  print_data();
    
  if (true == erased)
  {
    Serial.println("ERROR! The EEPROM is erased or the connection / phy addr is incorrect.");
    return;
  }

  if (true == z_filled)
  {
    Serial.println("ERROR! The EEPROM is Z filled.");
    return;
  }

  
  if (false == test_magic())
  {
    Serial.println("ERROR! Invalid magic number.");
    return;
  }

  prn_serial();
  
  mod_serial();
  reset_mileage();

  Serial.println("\n\rModified:");
  print_data();
  prn_serial();

  if ('m' != (char)key)
  {
    Serial.println("WARNING! The data was not modified in the EEPROM");
    return;
  }

  write_data(EERROM_PHY_ADDR);

  if (false == check_data(EERROM_PHY_ADDR))
  {
    return;
  }
  
  Serial.println("Fin");
}



void loop()
{
  //do nothing
}


(Про говнокод — да, не оптимизировал ни по памяти (читаются все данные в массив), ни по производительности (чтение и запись выполняется побайтно), ни по функционалу, но для такой простой задачи — и так сойдет!)

Прошиваем Ардуинку, открываем любой терминал (I18n, 9600бод), подойдет встроенный в Arduino IDE, сбрасываем платку, жмем любую кнопку:



После этого содержимое EEPROM будет прочитано, модифицировано, но не записано. Если процедура произошла без ошибок — снова сбрасываем плату, нажимаем m, после чего выполнятся все этапы и модифицированные данные будут записаны. Если что пошло не так, — повторно сверьтесь со схемой и повторите попытку. После удачного обнуления отпаиваем провода и устанавливаем картридж на место. Уровень тонера должен быть 100%.

Прошу прощения за «воду», инструкция из 3х строчек — очень мало для поста. Надеюсь, информация была полезной, и еще — автор не несет ответственности за возможные повреждения техники, лишение гарантийного обслуживания, все что Вы делаете — на Ваш страх и риск…

Еще один апдейт, дамп, полученный с моего картриджа:

Read from phy addr 3................................Ok
   0:  32  0  1  3  2  1  1  0  0  0 34 30 38 30 31 30 
  10:  16  5 4D 4D  1  2 11 70  0  0  0  0 14 14  5 21 
  20:  43 37 30 36 4D 39 30 33 31 39 35 20  0 45  0  0 
  30:  39  1  0  0  0  0  0  0 3E  4  0  0  0  0  0  0 
  40:   5  3  0  0  0  0  0  0  0  0  0  0  0  0  0  0 
  50:   0  0  0  0  0  0  0  0 14  E  5 1B 14  E  5 1B 
  60:   0  0  0  0  0  0  0  0 77  2  0  0  0  0  0  0 
  70:  C3 23 2A  0 16  0  0 55  0  0  0  0  0  0  0  0