Repository navigation
Firmware Embedded Java Application Layer
Part of Firmware Reversing. Resolves a question this wiki had open for a while: why do well-documented real RNS-510 features (Bluetooth hands-free, steering-wheel/MFSW integration, climate-control display, parking sensors) never appear anywhere in the native PowerPC C++ symbol/EH-descriptor table, no matter how it's searched?
A direct raw-byte string search across the complete 85.6MB FHDD6.FLI
(not just the 24MB native-code region this wiki otherwise focuses on)
for Bluetooth/climate/parking-sensor keywords immediately surfaced
literal Java class-path strings — class$vdo$most$dev$app$phon...,
Lvdo/dvc/pdc/impl/parrot/CT... — not native symbols. Pulling every
Lvdo/.../Lwdg/...-style Java class signature out of the whole file
found 2,806 distinct class paths, archived in full at
references/java_classpaths_2806.txt.
This is a real, substantial, previously entirely unexplored embedded
Java application layer — not a handful of stray classes.
Confirms the specific features directly:
-
Bluetooth:
SC_bluetooth_audio_EUR,BluetoothCon,BapPhoneListener(ties to the already-documented real BAP MOST-bus protocol — see HOST BSP and Vehicle Coding),HFP_ON/HFP_OFF(real Bluetooth Hands-Free Profile states),handsfreeOnOff,setRingerVolume,m_pairingTimer/m_pairingState.Lvdo/dvc/pdc/impl/parrot/CT...names Parrot — the real French automotive Bluetooth hands-free module supplier — as the actual vendor behind this subsystem. -
Climate control:
wdg.climate.ClimateZone,climateECUServiceableError/Status,notifyClimateECUServiceable,getClimateECUServiceable,solarRoofConfig/solarRoofError. -
Parking sensors:
OPSControlTracker(OPS = Optical Parking System),m_pdcDeffectMode(PDC = Park Distance Control, the real automotive term), matching thevdo/dvc/pdcJava package. - A separate, lower-level SPP (Serial Port Profile) init log
(
"SPP init: no reactor assigned","SSP: Startup V%2.2f") — C-style error-message phrasing, plausibly a native shim underneath the Java Bluetooth stack rather than part of it; not resolved further.
Clustering all 2,806 class paths by their 3rd-level package reveals a complete architecture — this is the real, user-facing application:
| Package | Classes | Role |
|---|---|---|
vdo/rns/app |
370 | The main RNS application itself |
vdo/nav/api |
362 | Navigation API |
vdo/rns/uii |
160 | RNS UI implementation |
vdo/uis/wdg |
105 | UI widgets |
vdo/most/dev |
94 | MOST-bus device layer |
vdo/jam/dev |
93 | "JAM" device layer (plausibly Java Application Manager) |
vdo/nav/cfc |
86 | Navigation "Cfc" classes — see below |
vdo/app/mm, vdo/svc/mm
|
82, 75 | Multimedia |
vdo/nav/ctrl |
64 | Navigation control |
vdo/bsl/util |
62 | "BSL" utility layer |
vdo/mm/cp2, vdo/mex/mm
|
58, 50 | Media/codec |
vdo/nav/tri |
53 | Plausibly trip computer |
vdo/app/mediaplayer |
34 | Media player |
vdo/s2i/pim |
29 | PIM (phone contacts/calendar) — "s2i" plausibly smartphone-to-infotainment sync |
vdo/mm/tv |
28 | TV tuner |
vdo/rns/speech, vdo/speech/asr
|
19, 12 | Speech/voice, incl. real ASR (Automatic Speech Recognition) |
vdo/dvc/pdc |
15 | Park Distance Control |
vdo/mm/dab |
13 | DAB radio |
vdo/svc/fm |
18 | FM radio |
vdo/sf/diag |
19 | Diagnostics |
Also present: a real standard Java class library subset —
java.awt (a full GUI toolkit: Container, GridLayout, event
listeners), javax.speech (Central, EngineCentral,
recognition.Recognizer, synthesis — a real, standard Java Speech
API implementation), javax.media (Codec, Renderer, PlugIn,
DataSource), javax.microedition.pim/io (J2ME-style mobile APIs).
This strongly suggests a CDC/J2ME-class embedded JVM with a fairly
complete AWT + Speech + Media API surface, not a minimal/stripped
runtime.
The app framework has a real session/factory architecture:
vdo/app/fwk includes AppFactoryAbstract, ApplicationInterface,
LaunchableApplication, AppContext, UserContext, and a session
factory with a genuine internal codename, SalsaSessionFactory —
worth remembering as a project/platform codename if it recurs
elsewhere.
A naming-convention link to the native C++ layer: vdo/nav/cfc
(86 Java classes) uses the exact same Cfc prefix this whole
wiki's native C++ work has run into constantly (CfcTypeCoordinate,
CfcMapViewProxy, CfcTypeRoadClassStatistic, dozens more). This is
very likely not a coincidence — plausibly the Java layer has classes
that mirror, wrap, or call into (via JNI or a similar bridge) the
native Cfc* C++ classes this project has been cataloguing all
session, consistent with "Cfc" being a cross-language internal
framework convention (plausibly "Common Framework Classes" or similar),
not something scoped to one language layer. Not independently
confirmed — a concrete lead for a future session.
Downloaded a real JDK (via the install-jdk pip package, which fetches
an Eclipse Temurin build) and the open-source CFR decompiler
(github.com/leibnitz27/cfr), then searched the whole 85.6MB file for
the real Java class-file magic number, CAFEBABE. 351 raw matches,
but most are coincidental 4-byte collisions, not real classes (a
from-scratch class-file parser — walking the constant pool, fields,
methods, and attributes to find each class's true end, since class
files don't declare their own total length — confirms only 17 are
genuine, well-formed class files; the rest fail with nonsense constant-
pool tags that don't exist in the real JVM spec). All 17 are real
Java 1.0/1.2-era class files (major version 45/46) and all 17
decompile cleanly to correct, readable Java source — CFR needed no
special handling. Real confirmed classes include:
// vdo.most.comm.FBlockObserver — the real MOST-bus FBlock interface
public interface FBlockObserver {
public static final short FBLOCK_REGISTERED = 257;
public static final short FBLOCK_UNREGISTERED = 258;
public static final short OVER_TEMPERATURE = 1;
public static final short UNDER_TEMPERATURE = 2;
public static final short HIGH_VOLTAGE = 3;
public static final short LOW_VOLTAGE = 4;
public static final short MECH_CHANGER_ERROR = 16;
public static final short MECH_LOADING_ERROR = 17;
public void fblockAvailabilityChanged(short var1, short var2, short var3);
}
// vdo.nav.api.map.types.map.MapPerspective — real 2D/2.5D/3D map modes
public class MapPerspective extends EnumDescription {
public static final MapPerspective MAP_PERSPECTIVE_2D = new MapPerspective(0, "MAP_PERSPECTIVE_2D");
public static final MapPerspective MAP_PERSPECTIVE_2_5D = new MapPerspective(1, "MAP_PERSPECTIVE_2_5D");
public static final MapPerspective MAP_PERSPECTIVE_3D = new MapPerspective(2, "MAP_PERSPECTIVE_3D");
}Also recovered: BTAudioPlayer/DefaultBTAudioItem/BTPremiumItem
(real Bluetooth audio classes, with BTPremiumItem extending
MdiItem — confirming Bluetooth audio is integrated as an MDI device
type, matching the unit's official "MDI interface (usb, iPod™)"
feature description exactly), TIItem (Traffic Information radio item
with real station-preset constants 1620/1629), MediaPlayer.State
(STOPPED/PLAYING/PAUSED/ERROR/NO_MEDIA),
ExitStreetNameResolver/StreetNameResolver (voice-guidance street
naming), AnnouncementPlayer, ICDNavigationVisibilityListener.
Why only 17 when 2,806 class names exist: these 17 are all small
(118-809 bytes) interfaces/enums/stub classes — far too few and too
small to be the real application. The overwhelming majority of the
real 2,806-class application is almost certainly stored in a
different, pre-linked/"romized" binary format rather than
individual standard class files — a common space-saving technique for
old embedded J2ME/CDC-class JVMs (e.g. Sun's own JavaCodeCompact
romizer tooling used exactly this approach). These 17 survived as
plain, standalone class files most likely because they're loaded
dynamically/reflectively at runtime (interfaces and small
implementation stubs are classic candidates for that), while the bulk
of the application was pre-processed into the ROM image and no longer
exists as individual CAFEBABE-prefixed files. Recovering the rest
is a separate, larger task: finding and reverse-engineering whatever
custom romized-class container format this JVM uses — not yet
attempted.
Full CFR output for all 17: archived at
references/java_decompiled_17classes.txt.
Direct, unambiguous string evidence: "(c) Copyright Insignia Solutions plc, 1998 - 2002", jeode.boot.library.path, EVM_HOME, com.jeode.evm.version,
java.vendor → "Wind Ri[ver]". Independently confirmed via public sources
(Insignia licensed Jeode's Embedded Virtual Machine to Wind River for
VxWorks/PowerPC; Jeode implements Sun's PersonalJava 1.1.1 and
EmbeddedJava 1.0.3 specifications — real, standard, now-obsolete
Java profiles from the late 1990s/early 2000s embedded-Java era).
A real preload/std/ClassPreloaderImpl class is referenced directly
(getConfigurationURL method name visible) — this is almost certainly
the actual romizer/class-preloading mechanism responsible for why only
17 of 2,806+ classes survive as standalone, standard CAFEBABE class
files. Checked whether ClassPreloaderImpl itself could be
recovered the same way — it can't: zero CAFEBABE markers exist
anywhere within 200KB of its own string reference, confirming its own
class body is embedded in the pre-linked/romized bulk, not a
standalone survivor. Fully cracking the romized container format is
a separate, substantial task — no technical specification is
publicly available for it (only Insignia/Wind River marketing
material), unlike the compiler-manual-driven win this project had
earlier for .frame_info. A concrete starting point for a future
session: the real on-disk/in-ROM container likely has its own
recognizable header structure (distinct from standard .class files)
immediately preceding the dense run of Lvdo/.../Lwdg/... name
strings — not yet investigated byte-by-byte.
Sources: EE Times — Insignia's Jeode Available for Wind River's VxWorks on PowerPC, Wind River press release.
The native PowerPC C++ code this wiki has spent this whole project
reverse-engineering (dbal, navicore, TMC, weather, SDARS
persistence, the EH/exception-handling mechanism, etc.) is real and
substantial, but it is a lower-level data/service layer, not the
user-facing application itself — the actual UI, navigation display
logic, media playback, Bluetooth/phone handling, voice/speech, and
climate/parking-sensor integration all live in this separate, much
larger (2,806+ classes) embedded Java layer this project has never
examined. This also retroactively explains the "Java-class content"
region found during the 9MB+ wall investigation (see
Firmware Reversing) — that was only one narrow
~4.24MB slice of a much bigger, pervasive Java presence spanning
tens of megabytes across the whole 85.6MB file (class-path string hits
were found from file offset ~1MB out to ~82MB in this session's
search).
UPDATE, done the same session — real class/image extraction now
works, and has its own reusable tooling in the project's main repo (not
this wiki): research/firmware_java_reader.py scans any firmware
file live for real Java class-path strings, real standalone .class
files, and real embedded screen images; research/ firmware_java_decompile.py decompiles recovered classes to actual
Java source via the open-source CFR decompiler, auto-installing a
portable JDK + CFR on first use (nothing binary committed to the
repo); rns510_firmware_viewer.py is a new tkinter GUI exposing all of
this (package tree, decompiled source, real screens) — a sibling to
rns510_map_viewer.py, not a change to it, since that tool renders the
map disc, not the firmware.
Results: of 351 raw CAFEBABE occurrences, only 17 are real,
well-formed class files (the rest are coincidental 4-byte collisions —
confirmed by writing a full class-file structure parser, since class
files don't declare their own length, and most "hits" fail on invalid
constant-pool tags that don't exist in the real JVM spec). All 17
decompile cleanly — see the earlier section above for real examples
(FBlockObserver, BTAudioPlayer, MapPerspective, etc.). Of image
signatures tried, GIF (6-byte magic, no real false-positive risk) found
11 real 800×480/100×50 images, of which 10 now decode (8 fully,
2 partially) — real per-brand "Software update" splash screens (SEAT,
Škoda, VW, and others) confirming the exact real screen resolution
visually, not just from widget-bounds strings, plus real UI widgets
like an actual "CANCEL" button icon:

Getting from 4 to 10 took 3 real, separate fixes, not just a bigger window — each targeting a different manifestation of the same underlying phenomenon this whole page keeps running into: small, localized, reproducible corruption scattered through this firmware dump, independent of file format.
-
A wrong color-table-size bit (most of the full-screen splash
images): several GIFs declare a wrong global-color-table-size field
in their Logical Screen Descriptor header — the declared size
claims more color-table bytes than are actually present, so the
real image descriptor sits earlier than the header says to look
(e.g.
0xf7where0xf5would give the correct, much smaller table — a single flipped bit). Fix: locate the real image descriptor by content instead of trusting the declared size — every real example here is a single full-screen frame startingleft=0,top=0,w=800,h=480— then rebuild a corrected header with the right color-table length. (_recover_gif_with_corrected_gct) This alone took recovery from 4 to 8 of 11. -
A falsely-claimed color table on small icons: a real 100×50 UI
button icon's image descriptor claims a local color table that
doesn't actually exist. Fix: rebuild assuming no color table at all
(Pillow then falls back to a default grayscale palette — real
pixel shape is recovered even though the true button color isn't)
and brute-force the real LZW-data start position, validating each
candidate with an actual decode rather than guesswork.
(
_recover_gif_icon_without_table) Recovered 2 real button icons this way — the first non-splash-screen, actual UI widget assets found in this firmware. -
Genuine corruption inside the compressed LZW codes themselves
(not just the header): for a few images, even a correctly-located
image descriptor and color table still won't decode — the damage is
inside the compressed data, the GIF counterpart to the streamed
Java classes' DEFLATE corruption. Standard LZW can't resynchronize
after one bad code (the code table is stateful), so there's no
clean "skip and continue." Built a from-scratch instrumented LZW
decoder that decodes as many genuinely-correct leading rows as
possible and stops at the first real corruption, returning only
that real partial image (
ImageInfo.partial=True) rather than nothing. Recovered 2 more this way — 59-60% of the image height decoded cleanly in both cases, enough to clearly identify a previously-unseen Škoda (red bar) and Volkswagen (green bar) variant screen, evidently a brand × status matrix of update screens:

All 3 fixes are wired into find_embedded_images() as generic,
content-validated fallbacks (_recover_gif_with_corrected_gct →
_recover_gif_icon_without_table → _recover_gif_partial_rows), not
one-off patches for specific offsets, so they apply to any firmware
file. The 1 remaining failure corrupts almost immediately (under 1% of
rows recoverable) and isn't worth returning.
Confirmed and ruled out: the file's already-known 24-85.6MB "Java/ VxWorks-kernel" region does contain real class-path strings (spanning roughly 1MB-82MB, not confined to one narrow band), but the bulk of the ~2,800-class application is NOT recoverable as standalone class files — it's pre-linked/"romized" into Jeode's own undocumented bundle format. That remains the real, still-open task (see the Jeode section below).
Separately from the standalone CAFEBABE class files above, this
firmware's real OSGi bundle packaging mechanism (vdo/rio/impl/fwk/ BundleManifest/Archive, referencing a real META-INF/MANIFEST.MF
and the standard OSGi manifest header vocabulary — Bundle-Name,
Export-Package, Bundle-ClassPath, Bundle-Activator, etc. — all
found as literal string constants inside that framework's own code)
stores some class entries as streamed ZIP local file header + DEFLATE
data pairs — general-purpose bit flag 3 set, meaning sizes/CRC32 are
deferred to a trailing data descriptor rather than declared up front.
Same false-positive lesson as every other magic-number search this
project has done: of 123 raw PK\x03\x04 hits, only a minority
validate against a strict filter (plausible filename bytes +
successful immediate DEFLATE decompression) — loosening the filter
(longer names, larger extra fields, bigger decompression window)
didn't find more exact matches (still 7), but checking the NAMES of
the 30 candidates that fail to decompress at all found they are ALSO
unmistakably real class names (ClimateControlAdapter.class,
DvdPlayer.class, JunctionType.class, even a brand-specific
ICDDisplayDeviceStateMachineMonochrom.class for Audi) — meaning at
least 37 real classes sit in this one narrow region alone, not just 7,
all blocked by the same underlying decompression problem. These are a
much richer catch than the 17 standalone classes: real
implementation classes, not just tiny interfaces/enums —
vdo/rns/app/icdent/impl/toolkit/DefaultBTAudioPlayer.class (890
bytes), vdo/rns/app/icdent/impl/std/ DefaultEntertainmentServiceBuilder$4.class (7,098 bytes — the largest
real class body recovered from this firmware so far).
Only 1 of 7 "exact match" entries currently decompiles cleanly
(DiagnosysService$1, a trivial empty anonymous class). The other 6
all reach a structurally valid DEFLATE end-of-stream (eof=True,
ruling out simple truncation) but produce a class file CFR can't parse
(corrupted-looking constant pool). Rigorously narrowed down, ruling
out 4 hypotheses in turn (the 4th — a premature BFINAL bit — was
this project's own earlier working theory, written up in a previous
session; a from-scratch instrumented decoder built to go fix it instead
disproved it):
- Multi-frame concatenation — ruled out: the leftover bytes after the first frame don't decompress as a 2nd standalone DEFLATE stream.
-
Shared preset DEFLATE dictionary — ruled out directly: tried
decompressing with several real candidate dictionaries (the one
working class, a standalone class, all 17 concatenated, even
reversed) via
zlib.decompressobj(-15, zdict=...)— the output is byte-identical regardless of dictionary, including with none at all, meaning the stream never actually references one. -
Wrong format/decoder bug — ruled out: tried standard zlib-wrapped
mode (
wbits=15/31/47, all fail outright — only raw deflate at the exact computed offset works at all) and cross-checked Python'szlibagainst Java's ownjava.util.zip.Inflater(the real, original JVM family this firmware's JVM descends from) on the exact same compressed bytes — byte-for-byte identical 890-byte output, ruling out a decoder implementation quirk. -
A premature
BFINALbit (the leading theory from the previous session) — also ruled out, by building a from-scratch, instrumented, bit-level raw-DEFLATE decoder (independent ofzlib/Inflater) that logs every block header and every decoded LZ77 symbol. Run withBFINALdeliberately ignored (so it would keep decoding past it looking for a hidden 2nd block), every one of these 7 entries turns out to be exactly one single DEFLATE block, withBFINAL=1set correctly and legitimately in the very first 3 bits of the stream — there is no 2nd block hiding afterward, and nothing about the block boundary is premature. (Each class really was compressed on its own, as a single short, complete, correctly-framed DEFLATE stream — unsurprising in hindsight.)
What's actually happening, now precisely isolated: the DEFLATE
decoding itself is 100% clean, for every byte, in every case — every
Huffman code and every LZ77 length/distance back-reference decodes
validly and in-bounds, all the way to a legitimate end-of-block symbol.
The corruption is already present inside the correctly-decompressed
bytes themselves — e.g. in DefaultBTAudioPlayer.class, a 50-byte
UTF8 constant-pool entry starts out perfectly (vdo/rns/app/icdent/,
reusing two genuine earlier back-references to identical text 43 bytes
back — completely normal LZ77 behavior) and then, mid-string, decodes
two literal bytes 'e', 0x00 where real path text should be — bytes
that a correctly-functioning compressor could only have emitted if they
were genuinely present in its input, since the Huffman/LZ77 decode
around them is provably lossless. This rules out a decompression bug
entirely: the corruption predates compression.
Two further findings pin this down hard:
-
It's reproducible byte-for-byte across unrelated firmware builds. The exact same
DefaultBTAudioPlayer.classentry, pulled from two completely different release trains (5238_MOD_C3_C4/5238_update_originalon one hand, and the much older3890build on the other), decodes to the exact same corrupted byte sequence (...icdent/e\x00e\x00...) at the exact same offset, before the two diverge further on (consistent with the rest of each build's constant pool differing). Storage bit-rot or a one-off extraction glitch would never reproduce identically across independently-built firmware images — this is a deterministic artifact of the original build toolchain. -
The damage is small and localized, not systemic — but its position varies per class, which rules out a simpler theory than it first looked like. A full structural walk of all 6 failing entries (not just reading CFR's one reported error) gives a clean 3-way split:
class constant pool class header ( access_flags/this_class/super_class/interfaces_count) right after itwhere it actually breaks TIPlayer$ActivationModeclean (22 entries) clean ( 0x0021,this_class=2,super_class=3, 0 interfaces — all valid)after 2 clean fields (real ConstantValueattributes) and one entire clean method (isValid, real descriptor(I)Z, a real, plausibleCodeattribute body:max_stack=2 max_locals=1 code_length=16) — breaks at the 2nd method's ownaccess_flags/name/descriptorBTPremiumItemclean (7 entries) garbage ( this_class=0,super_class=1799,interfaces_count=114)immediately at the cp/header boundary Mp3ActionEventclean (30 entries — independently re-confirmed by parsing the pool open-ended, ignoring the declared count entirely; it naturally stops at the exact same byte, ruling out a corrupted cp_countas the cause)garbage ( access_flags=0,this_class=25461,super_class=29703,interfaces_count=97)immediately at the cp/header boundary (not inside method bytecode as an earlier pass on this page guessed from CFR's error text alone — the real break is right here) DefaultBTAudioPlayerbreaks mid-pool (the garbled UTF8 entry documented above) — inside the constant pool itself BTPremiumPlayerBuilder$3,DefaultEntertainmentServiceBuilder$4also break mid-pool — inside the constant pool itself So corruption isn't anchored to any single structural landmark (not always "right after the pool", not always "deep in bytecode") — it shows up at a different relative position in each class. Combined with the earlier finding that it's byte-for-byte reproducible across unrelated firmware builds, this is most consistent with a reproducible bug in whatever original tool carved each class's bytes out of Jeode's one big romized blob — e.g. miscomputing which bytes to copy for a given class (not a random storage fault, which wouldn't reproduce identically across builds, and not a clean alternate format, since the garbage header values don't follow any consistent new convention either). The large index-like numbers seen in these header fields and in CFR's errors (
#15539,#25964,this_class=25461,super_class=29703) remain suggestively similar in magnitude to each other, keeping the "leftover reference into Jeode's larger shared romizer table" idea alive as a plausible reason the extraction tool reads the wrong bytes, but no consistent transformation between these numbers has been found yet, and it's no longer the leading open question — where the tool goes wrong per class is.
Follow-up this session: checked exactly that. Across all 6 failing
classes, the real/good content always runs out somewhere in the first
~100–500 decompressed bytes — regardless of the class's total
size (a 7,098-byte class and a 3,015-byte class both break well
under 500 bytes in; a 160-byte class, the only one with nothing beyond
that range, decodes 100% clean). That alone rules out anything tied to
total class size. Checked firmware offset (idx, data_start) modulo
every plausible block size from 128 bytes to 64KB (flash sector/erase
-block granularities) against each class's break offset — no
correlation at any of them, so it isn't simple physical-address
alignment either.
What does show a pattern: the garbage past the break point isn't
random noise. In Mp3ActionEvent.class, a 13-byte fragment of the
class's own real text (toolkit/Enter, part of the real class-path
string vdo/rns/app/icdent/impl/toolkit/EntertainmentEvent, itself
decoded cleanly earlier in the very same file) reappears in the
"corrupted" region 127 bytes later, amid runs of 0x00 padding and
other scrambled single-byte fragments; DefaultEntertainmentServiceBuilder$4
shows the same shape of damage — real substrings (ChangeSlotStrategy)
dissolving into short scrambled echoes and heavy zero-padding. This is
the same repeating-fragment signature noted in earlier passes on this
page — but this session independently proved, three different ways,
that our own ZIP/DEFLATE decoding is provably lossless and complete
end-to-end (the from-scratch bit-level decoder, and Python zlib vs.
Java's own Inflater producing byte-identical output). A real LZ77
-style decoding artifact appearing in bytes our own decoder reproduces
faithfully means that artifact must already have existed in the
plaintext fed into our compressor — i.e. it's inherited from an
earlier, upstream reconstruction step, not introduced by the ZIP
packaging this page has been analyzing.
Best-supported conclusion for now: the real fault lies further upstream than anything this page's tooling can see — most plausibly in Jeode's own ROM-compaction/compression scheme (itself apparently some kind of LZ-style dictionary format, given the repeating-fragment artifacts) failing or being imperfectly reconstructed by whatever tool pulled individual class bodies back out of the romized image, reliably recovering only each class's first ~100–500 bytes before the reconstruction goes wrong. This is close to the practical limit of what's recoverable without actual documentation or source for Jeode's romizer internals.
Chasing this further in a later pass turned up something concrete, not just speculation: Insignia Solutions itself holds a real, issued patent for exactly the kind of mechanism this page has been circumstantially inferring — US7240341B2, "Global constant pool to allow deletion of constant pool entries" (inventors Wayne Plummer and Jerry Kramskoy; see also its application publication US20030172194A1). It describes exactly this: a shared, global constant pool across the whole application, with per-class constants that are determined to be common/invariant moved out of each class's own ("load-unit specific") constant pool, after which the original entry is deleted. References to a moved constant are rewritten using a different, new opcode to distinguish a global lookup from a local one — the patent's own example:
rewriting fragment
LDC 14(meaning load the constant value at entry 17 in the load-unit specific constant pool) toLDCG 523(meaning load the constant value at entry 523 in the global constant pool)
This reframes this entire section: the "corruption" this page has
been chasing is very plausibly not corruption at all in the usual
sense, but this page's own tooling (and CFR) correctly reading
genuinely-Jeode-specific bytecode and structural fields using
standard-JVM-spec assumptions — e.g. CFR's "attempted to access
entry #15539" in a 29-entry pool, or this page's own
this_class=25461 found above, are exactly the shape you'd expect
from a real reference into a large global pool shared across the
whole ~2,800-class application, landing in a field our tooling still
interprets as "must be a small local index."
The patent is architectural, not an implementation spec — it doesn't
give the actual LDCG opcode byte value, its operand width/format, or
confirm whether the same global-pool trick extends beyond simple
constant loading to structural references like this_class/
super_class/field and method refs (its own text scopes it to
"constants" specifically). So this doesn't hand over a working
decoder, but it does turn "maybe a shared table, reason unknown" into
a specific, named, real, and citable mechanism — a much stronger place
to resume from than before.
Trying to act on the concrete next step above (hunting for a
global-pool opcode in TIPlayer$ActivationMode's bytecode) led
somewhere more directly useful first: this repo has FHDD6.FLI from
21 different firmware releases, spanning years of revisions
(3810 through 6276_original_update). Since streamed-class recovery
works on any firmware file, the obvious thing to try was comparing the
exact same class across all of them.
The result settles the question with real statistical weight, not speculation:
-
BTPremiumItem.classis byte-for-byte identical across all 21 builds — zero differences, anywhere, in any version, ever built for this product line. Its "garbage"-looking header (this_class=0,super_class=1799,interfaces_count=114, documented earlier on this page) isn't corruption at all by any reasonable definition of the word — it is the exact same, perfectly reproducible 113 bytes every single time. Whatever produces it does so completely deterministically. -
Mp3ActionEvent.classdiverges from the reference build at almost exactly the same byte (432, with a handful of builds off by 3-5 bytes due to small genuine content differences before that point) in every one of 20 builds — even though each build's total decompressed size varies wildly (1,608 to 3,015 bytes), meaning each build's real underlying method actually differs in length (the implementation evidently grew/changed over the product's lifetime), yet every single one independently hits trouble at the same spot. -
TIPlayer$ActivationMode.classshows the same thing: total sizes range from 403 to 5,512 bytes across builds, but every one matches the reference byte-for-byte for the first ~326-336 bytes before diverging.
This is far stronger evidence than anything else gathered this session (which relied on 2 builds at most). With 20+ independent data points, "random storage corruption" or "build-specific damage" are no longer remotely plausible — this is a fixed, per-class, build-independent cutoff, reproduced perfectly by whatever process has generated every single release of this firmware for over a decade. Different classes get different cutoffs (BTPremiumItem effectively "never", TIPlayer around 330, Mp3ActionEvent around 432) — but each class's own cutoff never moves, no matter how much its real implementation changes underneath it release to release.
That reframes the right way to think about this data entirely: this isn't "a complete class that got corrupted" — it's much more likely a fixed-size, per-class allocation (probably computed once, early, and simply never revisited even as the class grew) that was only ever designed to capture a bounded prefix, with whatever follows being incidental leftover content (not meaningfully "wrong", just not intended to be read past that point). A related but distinct public mechanism exists in this same technology family — US7096466, "Loading attribute for partial loading of class files into virtual machines" — describing deferred/lazy loading of whole methods or attributes marked by a dedicated attribute; it doesn't match what's observed here precisely (our cutoffs land mid-structure, not on clean method/ attribute boundaries), but confirms partial/bounded class loading was a known, deliberately-designed feature area for VMs in this lineage, not just an accident.
Acting on that next step directly: this repo holds FHDD6.FLI from 21
different firmware builds, which turned out to cover 156 distinct
real streamed-class names in total (not just the original 7 found on
one reference build) — 30 of them with 3+ independent build samples,
enough to measure a reliable cutoff for each. Correlating each class's
cutoff against several structural properties measurable from its own
(always-intact) constant pool:
| feature | correlation with cutoff |
|---|---|
constant-pool byte length (cp_end) |
r = 0.985 |
| total UTF8 bytes in the pool | r = 0.972 |
| constant-pool entry count | r = 0.899 |
| UTF8 entry count | r = 0.916 |
cp_end wins clearly. A linear fit across 16 classes gives
cutoff ≈ cp_end + 44 (slope 0.998, i.e. almost exactly 1-to-1),
with real classes scattered roughly ±25-50 bytes around that line —
genuinely strong (r² ≈ 0.97) but not perfect: Mp3ActionEvent, cross
-checked over all 20 of its builds specifically, has a constant
cp_end of 430 in every single one, yet its own cutoff sits right
around 432 — far closer to cp_end + 0 than cp_end + 44 for this
one class. So the right way to read this: the dominant factor by
far is each class's own constant-pool length, not some unrelated fixed
buffer size — exactly what you'd expect if whatever process
generates these entries correctly walks and copies the real constant
pool in full (whatever its length), then reliably captures only a
small, roughly-fixed amount of whatever comes after it.
Reusable tooling: research/firmware_java_reader.py's
find_streamed_zip_classes() implements the validated extraction
(real filename + decompressed bytes + whether it's fully_valid);
wired into rns510_firmware_viewer.py's class list alongside the
standalone classes (partial/corrupted ones are clearly flagged, not
hidden). Partial classes now also carry a
trustworthy_prefix_estimate (via _estimate_trustworthy_prefix()),
an honestly-caveated application of the formula above — e.g. it
correctly says BTPremiumItem.class's full 113 bytes are trustworthy
(matching its 21-build-identical result), while knowingly overestimating
for outliers like Mp3ActionEvent.class. Shown in the viewer's class
list as "~N bytes trustworthy" for any streamed class that isn't fully
valid.
Acting on the formula directly: 156 distinct real streamed-class names are findable across this repo's 21 firmware builds (not just the original 7 on one build), and an obvious next step is to try patching each one into something CFR can read at all, using only its real, intact constant pool.
A first attempt — truncate to the pool, then declare 0 fields/0
methods — proved pointless: CFR happily decompiled the result, but
with nothing in it (public abstract class X extends X {}), since
there was nothing left to print.
The useful version mines the pool itself for real content first:
Fieldref/Methodref constant-pool entries whose "class" portion
references the class itself are an extremely common, naturally
-occurring pattern (a class reading its own field, or calling its own
method), and their NameAndType entries hand over the real field/
method name and real type signature — straight from data that's
already independently confirmed intact. Declaring the mined fields as
plain public fields and the methods as public abstract (so no real
Code attribute/bytecode is needed at all) is enough for CFR to print
them.
Across all 156 names, this recovers real signatures — not just an empty shell — for 29 classes, including genuinely interesting real application data:
// real ignition/power states (VW "clamp 15/30/50" terminology)
public abstract class VehicleClampStatusEvent {
public short m_clampS;
public short m_clamp15;
public short m_clamp50;
}
// real RDS (radio data system) fields
public abstract class FMListEntryInfo {
public int pICode;
public int freq;
public String psName;
public short pTYCode;
public short trafficProgramInfo;
public short globalQual;
}
// real touchscreen calibration data
public abstract class CalibrationPoint {
public int m_expectedX;
public int m_expectedY;
public int m_actualX;
public int m_actualY;
}This is real signatures, not real behavior — method bodies are always
fabricated stand-ins, never the true implementation, and this is
clearly labeled wherever it's shown (the method/field shape is real
and useful architectural information; what the code actually does
inside is not recoverable this way). research/ firmware_java_reader.py's reconstruct_class_signatures()
implements this, and rns510_firmware_viewer.py falls back to it
automatically whenever a normal decompile of a partial/corrupted
streamed class fails outright, with a clear on-screen note that what
follows is signatures-only.
Two further scaling attempts, tried and ruled out: (1) the other
firmware component images in this repo (RADIO.FLI, DAB.FLI,
GATEWAY.FLI, SWL.FLI, MPEGAPPS.FLI, etc.) were checked for any
embedded Java at all — zero class-path strings, zero CAFEBABE hits,
zero streamed-ZIP headers in any of them; the Jeode layer lives
exclusively in FHDD6.FLI (unsurprising — those other images are
almost certainly native firmware for other ECUs in the car, e.g. the
radio tuner and CAN gateway, not the Java-based nav/infotainment head
unit). (2) Tolerating a constant pool that breaks partway through
(salvaging whatever entries parsed validly before the break, instead
of requiring the whole pool to parse cleanly) looked promising at
first — it can still return plausible-looking synthesized bytes — but
real decompile attempts show why reconstruct_class_signatures()
deliberately requires a fully clean pool: once the pool's internal
consistency breaks at one point, index values that still parse
structurally can resolve to the wrong entry type elsewhere (CFR:
ClassCastException: ConstantPoolEntryUTF8 cannot be cast to ConstantPoolEntryClass) — confirmed to add zero net-new real
recoveries across all 156 class names when actually tested through
CFR, not just checked for "did bytes come out."
With the Java-recovery avenue thoroughly mined for now (and the last
two scaling attempts above both coming back negative), this session
pivoted to the thing all this recovery work was ultimately in service
of: rns510_firmware_viewer.py now has a Simulator tab that
assembles what's actually been recovered into a real, clickable mockup
rather than leaving it as separate inspector tabs.

Everything shown is real and computed live from whatever firmware file is opened — nothing is hardcoded:
- Boot screen: cycles through every real, fully-decoded 800×480 splash image actually recovered from the opened firmware (typically 8-10 — see the GIF-recovery sections above), with Prev/Next controls and a caption showing each one's real offset and dimensions, rather than only ever showing the same one:

-
Menu grid: the real
vdo/rns/app/*package tree — starting from the top-level names (nav,icdmgr,icdent,mda,rdo,commgr, ...), sized by how many real classes were actually found under each. Drillable: each button descends one level further into the real package structure (with a breadcrumb + Back button), bottoming out at the real leaf class names once there's no more real sub-package structure to descend into — real structural information the whole way down, not invented labels at any level. - CANCEL button: the real recovered 100×50 button-icon asset, used as an actual clickable widget.

What this is NOT: real, verified menu logic. Drilling down just walks the real package tree — there's no recovered navigation/dispatch bytecode behind it, since the bulk of the real application is still pre-linked into Jeode's romized format this page documents working on above. The tab says so directly on-screen, not just here. This is a first-pass visual skeleton — honest about what's real (the assets and the package tree) versus placeholder (the navigation logic) — not a finished simulator.
Connected to real decompiled source: ClassFileInfo (standalone
classes) now carries the class's own real name too, resolved from
this_class during the same constant-pool walk find_class_files
already does — matching what StreamedClassInfo already exposed. When
the Simulator's drill-down reaches real leaf class names, any that
also happen to be one of this firmware's real decompilable classes
(standalone or streamed) render as a clickable green button instead of
a plain label; clicking jumps straight to the Decompiled Classes tab
and shows the real source. This is genuinely narrow in practice — most
of a package's real classes aren't independently recoverable as full
class files, so most leaf names stay plain labels — but on the
reference firmware it does work, e.g. drilling into
vdo/rns/app/icdmgr/std surfaces ICDNavigationVisibilityListener as
a real, clickable, fully-decompiled interface:

This also caught and fixed a real gap in the drill-down itself: a
package can legitimately have both real sub-packages and real
classes declared directly at the same level (as icdmgr/std does
above — 2 sub-packages, 16 direct classes) — the first version of the
drill-down only ever showed one or the other.
Opens directly from a firmware ISO, no extraction: File > Open Firmware now accepts a firmware ISO directly (alongside a raw .FLI
file) — the main apps image (FHDD*.FLI) is located and read straight
out of the ISO's own byte range via rns510_iso.py's existing
find_file()/open_file_ref(), the same no-extraction, direct-from
-ISO pattern already used elsewhere in this project (research/ swl_5238_reader.py's find_screen_resolution()). Nothing is ever
written to a separate extracted file on disk. Verified byte-for-byte
(sha256 match, in ~0.3s) against a separately-extracted copy, and the
full viewer — status bar, class/image counts, Simulator — works
correctly end to end when opened this way:

Swept across every firmware ISO in this repo (27 distinct real
discs, spanning this project's full range of eras/variants) as a
follow-up check: every one resolved correctly, in 0.06-0.35s each, zero
exceptions. Notably this included real cases find_file()'s own
docstring specifically calls out but hadn't actually been exercised
end-to-end before: North-America discs (RNS510_4366.ISO, US_RNS -510_SW1140.iso) correctly resolving the per-variant NARBY/PROG/
path rather than the standard RNSMIDEC/PROG/ one, and the older
FHDD.FLI (no "6") naming on SWL_CD_1020_1022.iso and
firmware_900.iso. The 2 non-firmware discs in the archive (CD_8555 .ISO, the map disc; VIM_Berto89.iso) correctly reported "not found"
rather than crashing.
import re
with open("FHDD6.FLI", "rb") as f:
data = f.read()
# Real Java class-path signatures (JVM constant-pool UTF8 entries referencing
# class names) live as plain ASCII throughout the whole 85.6MB file, not just
# the 24MB native-code region.
pat = re.compile(rb"L(vdo|wdg)/[A-Za-z0-9_/]{3,80}")
classpaths = {m.group(0) for m in pat.finditer(data)}
# 2,806 distinct paths found this session; see references/java_classpaths_2806.txtFor real class-file/image extraction and decompilation, use the project's
own research/firmware_java_reader.py and research/ firmware_java_decompile.py (not duplicated here — see those modules'
own docstrings for the full API), or just run the new
rns510_firmware_viewer.py GUI directly.
Road network
- eeu.rd
- eeu.il
- eeu.iof
- eeu.typ
- rt / rl / prl
- eeu.si (segment classification)
- eeu.tmc (TMC traffic data)
Places
- eeu.ctr
- cty / cl / ct
- eeu.cny (counties)
- eeu.stt (states/regions)
- eeu.ti (time-info/DST)
- pmc / pmm (postal-code merge)
- pol / pot / pmp (postal-code, empty)
Language / TTS support
- eeu.abc (alphabet)
- eeu.aff (affix)
- eeu.cal (country names)
- eeu.cat (country bboxes)
- prd (road phonetics)
- pct (city phonetics)
- pcl (phoneme cluster list)
- pca (country phonemes)
Map tiles
Container formats
Cross-cutting
- eeu.mod (schema dictionary)
- Road naming join
- Header trailer fields
- Map viewer tool
- Road editor tool
- Unexamined files
- Disc-root build script & subsystems
Firmware reversing