Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ or firmware changes may not align exactly with calendar weeks.

| Identification | Observed manufacturing codes | What is known |
|---|---|---|
| `EF-S7-V1.0.30B` / Ingenic T23 / 8 MiB `ZB25VQ64` family | `0226`, `0526` (two units), `1526` | This is the supported layout on which the failure has been observed. Continue below. |
| `EF-S7-V1.0.30B` / Ingenic T23 / 8 MiB `ZB25VQ64` family | `0226`, `0526` (two units), `1526`, `2526` | This is the supported layout on which the failure has been observed. Continue below. |
| `EF-S7-V1.0.30B` / Ingenic T23 / 16 MiB `P25Q128H` family | `4025` | This early layout is materially different. The known erase defect has not been established on it, and the current fix does not apply. |
| `EF-S7-V1.0.30D` / likely TX5110 | Not established | This revision is not affected by the known failure. This guide does not apply. |
| Any other revision, processor or flash layout | Not established | It has not been investigated. We do not know whether it is affected, and this guide does not apply. |
Expand Down
5 changes: 3 additions & 2 deletions cc2camera/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,9 @@ def command_identify_camera(args) -> int:
)
print(
"30B includes more than one flash-layout variant. The affected 8 MiB "
"variant has been observed with PCB date codes 0226, 0526 and 1526; "
"an earlier 4025 unit uses a different, unsupported 16 MiB layout."
"variant has been observed with PCB date codes 0226, 0526, 1526 and "
"2526; an earlier 4025 unit uses a different, unsupported 16 MiB "
"layout."
)
print(
"The stream cannot distinguish those variants. Date codes are supporting "
Expand Down
10 changes: 6 additions & 4 deletions cc2camera/image.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""
Strict, self-contained recovery builder for the Elegoo Centauri Carbon 2
stock camera firmware family observed in four independent 8 MiB dumps.
stock camera firmware family observed in five independent 8 MiB dumps.

The tool:
* validates every invariant firmware byte against fingerprints derived
Expand Down Expand Up @@ -82,7 +82,9 @@
HW_UOID_END = 0x7D206F
HW_DATE_START = HW_UOID_END
HW_DATE_END = 0x7D2073
HW_SUPPORTED_DATES = frozenset((date(2026, 3, 2), date(2026, 4, 1)))
HW_SUPPORTED_DATES = frozenset(
(date(2026, 3, 2), date(2026, 4, 1), date(2026, 4, 17))
)

JFFS2_MAGIC = 0x1985
JFFS2_NODE_ACCURATE = 0x2000
Expand All @@ -99,8 +101,8 @@
b"dev_config.cfg",
}

SERIAL_PATTERN = re.compile(rb"^serial=(12PSSSS[34][A-Z0-9]{28})\n$")
UOID_PATTERN = re.compile(rb"^12PSSSS[34][A-Za-z0-9+/=]{86}$")
SERIAL_PATTERN = re.compile(rb"^serial=(12PSSSS[345][A-Z0-9]{28})\n$")
UOID_PATTERN = re.compile(rb"^12PSSSS[345][A-Za-z0-9+/=]{86}$")

# Exact byte ranges shared by every supported HWCONFIG variant.
REFERENCE_SEGMENTS = {
Expand Down
2 changes: 1 addition & 1 deletion hardware-recovery/CC2_RECOVERY_VERIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
> audit record, not as the current compatibility contract. See
> [TECHNICAL_DETAILS.md](TECHNICAL_DETAILS.md) for the current supported
> HWCONFIG/identity structures and [TEST_RESULTS.md](TEST_RESULTS.md) for the
> four-camera validation record.
> five-camera validation record.

Review date: 2026-08-22
Reviewed tool: `cc2_sig_tool.py` v1.1.0
Expand Down
18 changes: 14 additions & 4 deletions hardware-recovery/REFERENCE_FINGERPRINTS.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@
"check_range": "0x7D200A-0x7D200C",
"date_encoding": "little-endian uint16 year, uint8 month, uint8 day; only physically observed values are accepted",
"date_range": "0x7D206F-0x7D2072",
"observed_date_values": ["2026-03-02", "2026-04-01"],
"serial_prefixes": ["12PSSSS3", "12PSSSS4"],
"uoid_prefixes": ["12PSSSS3", "12PSSSS4"]
"observed_date_values": ["2026-03-02", "2026-04-01", "2026-04-17"],
"serial_prefixes": ["12PSSSS3", "12PSSSS4", "12PSSSS5"],
"uoid_prefixes": ["12PSSSS3", "12PSSSS4", "12PSSSS5"]
},
"reference_images": [
{
Expand Down Expand Up @@ -109,9 +109,19 @@
"label": "Fourth camera bricked image",
"sha256": "574c71e6572093ea1b65c3ca9c3e5e1dd1e55d71443ca7b282db8ce700c45c85",
"state": "stock system, exhausted config, 12PSSSS3 identity, 261-byte HWCONFIG record"
},
{
"label": "Fifth camera bricked image",
"sha256": "f41e33b9927cfd8a5b11180872a2b940d645ad33ca3d13d5271476e930eb352e",
"state": "stock system, exhausted config, 12PSSSS5 identity, 2026-04-17 HWCONFIG date, 261-byte HWCONFIG record"
},
{
"label": "Fifth camera permanent full readback",
"sha256": "45acd32f451428513f14ca1238815e52a0508636060ef6973fa3ebafe0228a41",
"state": "audited bashrc patch, rebuilt config, physically boot-tested"
}
],
"scope": "Exact CC2 stock-camera firmware family, 12PSSSS3 and 12PSSSS4 identities, and the 256- and 261-byte type-12 payload lengths observed in four independent cameras; unit check/date fields and five-byte extension contents are normalized",
"scope": "Exact CC2 stock-camera firmware family, 12PSSSS3, 12PSSSS4 and 12PSSSS5 identities, and the 256- and 261-byte type-12 payload lengths observed in five independent cameras; unit check/date fields and five-byte extension contents are normalized",
"segments": {
"boot": {
"end_exclusive": "0x040000",
Expand Down
31 changes: 16 additions & 15 deletions hardware-recovery/TECHNICAL_DETAILS.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,15 @@ compatibility boundary:

| Observed layout | Manufacturing-code observations | Evidence |
|---|---|---|
| Supported 8 MiB `ZB25VQ64` family | `0226`, `0526` (two units), `1526` | The failure was reported on all four cameras. The `1526` unit and one recovered bricked unit have been physically recovered or protected with this fix. |
| Supported 8 MiB `ZB25VQ64` family | `0226`, `0526` (two units), `1526`, `2526` | The failure was reported on all five cameras. The `1526` unit and two recovered bricked units, including the `2526` unit, have been physically recovered or protected with this fix. |
| Early 16 MiB `P25Q128H` family | `4025` | Two supplied full dumps share the 16 MiB partition map. Photographs show the T23, 30B PCB label, manufacturing code and 128-Mbit flash. Their JFFS2 cleanmarkers occur at `0x8000` intervals, matching an erase size handled by their kernel. The known `0x4000` erase mismatch has not been established on this layout. |

The early dumps were analyzed privately and remain outside the repository.
The 16 MiB family is not accepted by the builder or installers. A manufacturing
code, PCB revision or stream signature never overrides firmware, capacity,
partition-map or erase-geometry validation.

This release intentionally accepts only the exact firmware family already verified in four independent camera dumps with different unit identities:
This release intentionally accepts only the exact firmware family already verified in five independent camera dumps with different unit identities:

- 8 MiB `ZB25VQ64` SPI NOR
- Ingenic T23N
Expand All @@ -147,8 +147,8 @@ string. JFFS2 appends new nodes on each boot, so two otherwise identical
cameras naturally have different raw `config` histories. The HWCONFIG
partition also contains a per-unit UOID, an associated three-byte value, and a
four-byte little-endian year/month/day field. The date interpretation is based
on the observed `2026-03-02` and `2026-04-01` byte sequences; its exact vendor
semantics remain unknown.
on the observed `2026-03-02`, `2026-04-01`, and `2026-04-17` byte sequences;
its exact vendor semantics remain unknown.

The known 256-byte prefix of its type-12 length-prefixed record is validated.
Two payload lengths have been directly observed:
Expand All @@ -165,12 +165,13 @@ The record length exactly includes the five added bytes. The camera's own
`ucamera` executable advances over this structure as a little-endian 16-bit
type, a little-endian 16-bit payload length, and that many payload bytes.

The observed serial and UOID families begin with either `12PSSSS3` or
`12PSSSS4`. Both retain the same total lengths and remaining character
The observed serial and UOID families begin with `12PSSSS3`, `12PSSSS4`, or
`12PSSSS5`. All retain the same total lengths and remaining character
structure. The fourth camera established that the adjacent check field spans
three bytes and that the calendar-shaped field varies by unit. Both fields are
preserved exactly and validated structurally rather than treated as firmware
bytes.
three bytes and that the calendar-shaped field varies by unit. The fifth
camera established the `12PSSSS5` family and `2026-04-17` date value. Both
fields are preserved exactly and validated structurally rather than treated
as firmware bytes.

The meaning of the five-byte extension is unknown, so its value is not treated
as a firmware invariant. The validator requires record type 12 and one of the
Expand Down Expand Up @@ -204,7 +205,7 @@ The validator therefore performs the strongest safe equivalent of “100% match
- an all-zero or all-`FF` check value is reported as a warning;
- the UOID must have an observed prefix and the expected 94-byte structure;
- the four-byte date must decode as one of the physically observed calendar
values, `2026-03-02` or `2026-04-01`;
values, `2026-03-02`, `2026-04-01`, or `2026-04-17`;
- `config` must contain CRC-valid JFFS2 nodes;
- serial-only accepts only the six known filenames unless
`--wipe-unknown-config` is explicit;
Expand Down Expand Up @@ -244,11 +245,11 @@ only for the observed five-byte extension. A future Elegoo/Jovision build,
another payload length, or a different record type or known-prefix layout must
be analyzed and fingerprinted separately.

Four real unit identities were directly inspected, including two with the
261-byte HWCONFIG record and one `12PSSSS3` identity. Invariant firmware and
the known HWCONFIG prefix must match exactly after unit-field and extension
normalization, while the UOID and serial must each match their observed
structure.
Five real unit identities were directly inspected, including three with the
261-byte HWCONFIG record, one `12PSSSS3` identity, and one `12PSSSS5`
identity. Invariant firmware and the known HWCONFIG prefix must match exactly
after unit-field and extension normalization, while the UOID and serial must
each match their observed structure.
All unit-specific HWCONFIG bytes and the recovered serial payload are preserved
exactly. Because the proprietary full serial↔UOID derivation is unknown, the
tool does not infer or enforce a relationship between those independently
Expand Down
41 changes: 38 additions & 3 deletions hardware-recovery/TEST_RESULTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,38 @@ one representative file, rather than all independently acquired copies, was
available in the test environment. This is software validation only; the
generated image was not flashed or boot-tested.

## `12PSSSS5` identity and `2026-04-17` HWCONFIG validation

Validation date: 2026-10-24

A fifth physical `EF-S7-V1.0.30B` camera with manufacturing code `2526`, an
Ingenic T23 processor, and an 8 MiB `ZB25VQ64D`-family flash supplied three
consecutive byte-identical reads. The reads were acquired at 1 MHz through a
Pico-compatible `serprog` programmer without moving the SOIC-8 clip. All
copies have SHA-256
`f41e33b9927cfd8a5b11180872a2b940d645ad33ca3d13d5271476e930eb352e`.
The raw dumps and full unit identifiers remain outside the repository.

- Boot, kernel, root, every invariant system byte, the stock startup-script
window, and the normalized HWCONFIG fingerprint match the supported firmware
exactly.
- The type-12 record has the observed 261-byte shape and five-byte extension
`00 00 00 08 40`.
- The serial and UOID use the structurally matching `12PSSSS5` family while
retaining their established lengths and character sets.
- The calendar-shaped field at `0x7D206F–0x7D2072` decodes to the newly
observed value `2026-04-17`.
- Config was 98.457% non-`FF`, with 659 CRC-valid nodes, 657 obsolete nodes,
and only a live CRC-valid `serial.cfg`.
- A three-read serial-only recovery build passed complete post-build
validation. It preserved HWCONFIG byte-for-byte and changed only the audited
startup-script window and config partition.
- The generated recovery image has SHA-256
`45acd32f451428513f14ca1238815e52a0508636060ef6973fa3ebafe0228a41`.
After programming, a complete 8 MiB readback was byte-for-byte identical to
that image. The camera then booted, enumerated normally, and produced a
working video feed.

## Independent filesystem and patch checks

- A separate read-only SquashFS v4/XZ parser extracted `/home/bashrc.sh` from stock and generated images.
Expand All @@ -110,7 +142,7 @@ generated image was not flashed or boot-tested.
bytes passed strict analysis and recovery generation; the generated image
retained HWCONFIG byte-for-byte. This is software validation, not a physical
flash or boot test of that camera.
- Four distinct real serial/UOID identities were inspected; the available
- Five distinct real serial/UOID identities were inspected; the available
full-ROM regression inputs were accepted and preserved.
- An additional structurally valid synthetic identity with two confirmation
reads (three total): accepted and preserved.
Expand All @@ -126,6 +158,9 @@ generated image was not flashed or boot-tested.

## Scope of the recovery claim

Software-level recovery is verified: the available bricked dumps are transformed into strict, canonical images with the documented hashes, readable patched startup logic, preserved unit identity, and valid filesystem structures. One output also equals the earlier recorded permanent readback hash.
Software-level recovery is verified: the available bricked dumps are transformed into strict, canonical images with the documented hashes, readable patched startup logic, preserved unit identity, and valid filesystem structures. Two outputs have recorded permanent full readbacks equal to their generated images.

Hardware-level recovery is not newly proven by this review. That requires flashing a physical bricked camera, making a full byte-identical readback, and observing a successful boot/USB enumeration. The tool's generated `FLASHING.txt` makes that the required final validation step.
Hardware-level recovery is verified on the fifth camera by a controlled flash,
complete byte-identical readback, successful boot/USB enumeration, and working
video feed. The tool's generated `FLASHING.txt` retains those checks as the
required final validation sequence for each camera.
2 changes: 1 addition & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def test_identify_camera_is_read_only_and_reports_known_family(self):
hid.assert_not_called()
report = output.getvalue()
self.assertIn("EF-S7-V1.0.30B", report)
self.assertIn("0226, 0526 and 1526", report)
self.assertIn("0226, 0526, 1526 and 2526", report)
self.assertIn("4025", report)
self.assertIn("stream cannot distinguish", report)
self.assertIn("read-only family-identification aid", report)
Expand Down
13 changes: 9 additions & 4 deletions tests/test_hwconfig_variants.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ def hwconfig_record(length: int, extension: bytes = b"") -> bytes:

class HwconfigVariantTests(unittest.TestCase):
def test_observed_identity_prefixes_are_supported(self):
for family in (b"3", b"4"):
for family in (b"3", b"4", b"5"):
with self.subTest(family=family.decode("ascii")):
serial = b"serial=12PSSSS" + family + b"A" * 28 + b"\n"
uoid = b"12PSSSS" + family + b"A" * 86
self.assertTrue(tool.serial_payload_is_valid(serial))
self.assertIsNotNone(tool.UOID_PATTERN.fullmatch(uoid))

def test_unobserved_identity_prefixes_are_rejected(self):
for family in (b"2", b"5"):
for family in (b"2", b"6"):
with self.subTest(family=family.decode("ascii")):
serial = b"serial=12PSSSS" + family + b"A" * 28 + b"\n"
uoid = b"12PSSSS" + family + b"A" * 86
Expand Down Expand Up @@ -96,10 +96,10 @@ def test_unit_fields_are_normalized_without_hiding_neighbors(self):
observed = bytearray(hwconfig_record(0x105, bytes.fromhex("0000000840")))
observed[tool.HW_CHECK_START:tool.HW_CHECK_END] = b"\x04\x05\x06"
observed[tool.HW_UOID_START:tool.HW_UOID_END] = (
b"12PSSSS3" + b"B" * 86
b"12PSSSS5" + b"B" * 86
)
observed[tool.HW_DATE_START:tool.HW_DATE_END] = (
(2026).to_bytes(2, "little") + bytes((3, 2))
(2026).to_bytes(2, "little") + bytes((4, 17))
)

original = bytes(original)
Expand Down Expand Up @@ -132,6 +132,11 @@ def test_hwconfig_date_is_validated(self):
)
self.assertEqual(tool.decode_hwconfig_date(bytes(image)), "2026-04-01")

image[tool.HW_DATE_START:tool.HW_DATE_END] = (
(2026).to_bytes(2, "little") + bytes((4, 17))
)
self.assertEqual(tool.decode_hwconfig_date(bytes(image)), "2026-04-17")

image[tool.HW_DATE_START:tool.HW_DATE_END] = (
(2026).to_bytes(2, "little") + bytes((2, 30))
)
Expand Down
Loading