Nwagyu!Nwagyu!
App installer
  • English
  • Français
GitHub
App installer
  • English
  • Français
GitHub
  • Documentation Index
  • External apps
    • Creating your own application
    • Accessing storage
    • On/Off and Home keys
    • Syscalls
  • Firmware
    • Boot process
    • Slots
    • Bootloader
    • Kernel
    • Userland
    • Addresses and structures
  • Others
    • Communication with the computer (DFU)
    • Downloading Epsilon from NumWorks' website
  • User documentation

Addresses and structures

The calculator has a lot of headers and parsable structures about a lot of features: it's used for storage, current slot, username, current version…

The Upsilon.js library was designed to parse them with backward-compatibility. On this page, we will only describe structures for recent Epsilon versions (16+), which are also used on custom firmwares for unlocked calculators.

Determining device model

When reading structs, you need to know the calculator model you are using, especially when you want to read the Slot Info.

Based on bcdDevice

Info

This method is intended to be used from a computer, for example by Upsilon.js.

For N0115 and N0120, this method is always reliable and the easiest one to implement. There won't be any false positive or false negative as they only run recent Epsilon versions implementing bcdDevice.

If bcdDevice indicate N0110, it will be an N0110. However, the reverse is not true: on N0110, before Epsilon 17 and on custom firmwares, was the same as N0100, so you should fall back to other detections method in case you detect N0100. In case you are interested by the commit adding this feature, it's d63617 on Epsilon repository.

From you computer shell, you can detect it using lsusb -v

Output for an N0110 calculator running Epsilon 23
$ lsusb -v
Bus 001 Device 071: ID 0483:a291 STMicroelectronics NumWorks Calculator
Negotiated speed: Full Speed (12Mbps)
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.10
  bDeviceClass            0 [unknown]
  bDeviceSubClass         0 [unknown]
  bDeviceProtocol         0 
  bMaxPacketSize0        64
  idVendor           0x0483 STMicroelectronics
  idProduct          0xa291 NumWorks Calculator
  bcdDevice            1.10 # <<< Here's the model <<<
  iManufacturer           1 NumWorks
  iProduct                2 NumWorks Calculator
  iSerial                 3 XXXXXXXXXXXXXXXX
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x0024
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              100mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           0
      bInterfaceClass       254 Application Specific Interface
      bInterfaceSubClass      1 Device Firmware Update
      bInterfaceProtocol      2 
      iInterface              4 @Flash/0x90030000/61*064Kg,64*064Kg
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       1
      bNumEndpoints           0
      bInterfaceClass       254 Application Specific Interface
      bInterfaceSubClass      1 Device Firmware Update
      bInterfaceProtocol      2 
      iInterface              5 @SRAM/0x20000000/01*252Ke
      Device Firmware Upgrade Interface Descriptor:
        bLength                             9
        bDescriptorType                    33
        bmAttributes                        3
          Will Not Detach
          Manifestation Intolerant
          Upload Supported
          Download Supported
        wDetachTimeout                      0 milliseconds
        wTransferSize                    2048 bytes
        bcdDFUVersion                   1.00
Binary Object Store Descriptor:
  bLength                 5
  bDescriptorType        15
  wTotalLength       0x001d
  bNumDeviceCaps          1
  Platform Device Capability:
    bLength                24
    bDescriptorType        16
    bDevCapabilityType      5
    bReserved               0
    PlatformCapabilityUUID    {3408b638-09a9-47a0-8bfd-a0768815b665}
      WebUSB:
        bcdVersion    1.00
        bVendorCode      1
        iLandingPage     1 https://my.numworks.com
Device Status:     0x0000
  (Bus Powered)

However, the calculator is often used from WebUSB, so here's a simple JavaScript implementation (from Upsilon.js):

let usbDeviceVersion = "" + this.device.device_.deviceVersionMajor + this.device.device_.deviceVersionMinor + this.device.device_.deviceVersionSubminor
switch (usbDeviceVersion) {
    case "120":
        return "0120";
    case "115":
        return "0115";
    case "110":
        return "0110"
    // We can't match on N0100 as some N0110 firmwares are returning 100
}

Based on flash size

Info

This method is intended to be used from a computer, for example by Upsilon.js.

bcdDevice allows reducing the possible models to 2: N0100 and N0110. The main difference between N0100 and N0110 is the presence of an external flash.

On recent Epsilon versions, the internal flash is hidden.

The logic could be simplified, but I don't have time to test on every configuration, so just read the code of the getModel function.

Based on FCC ID

Info

This method is intended to be used from NWA external apps

There is an SVC call named SVC_FCC_ID that return a const char * (in register R0) pointing to a string containing the FFC ID, as visible in the settings of the calculator under the "About" submenu.

On my N0110, the string is defined as 2ALWP-N0110, so the model can be determined this way.

This method is isn't implemented at the time on any external app, but it's probably one of the most reliable way to do so

Based on installed slots

Info

This method is intended to be used from NWA external apps

Unlike the previous method, this one can have false positives.

The location of the userland header is different between N0110 and N0120 (see the userland header section), so we can exploit this difference.

On NumWorks Extapp Storage, that's the method that's used (simplified for easier reading):

C implementation, collapsed as the code is quite long
// Function to reverse the endianness, not required but allow writing code in
// the right endianness for reading and calculator at the same time
inline uint32_t reverse32(uint32_t value) {
  return (((value & 0x000000FF) << 24) |
          ((value & 0x0000FF00) <<  8) |
          ((value & 0x00FF0000) >>  8) |
          ((value & 0xFF000000) >> 24));
}


const uint8_t extapp_calculatorModel() {
    uint32_t * userlandMagicSlotAN0110 = *(uint32_t **)0x90010000;
    uint32_t * userlandMagicSlotBN0110 = *(uint32_t **)0x90410000;
    uint32_t * userlandMagicSlotAN0120 = *(uint32_t **)0x90020000;
    uint32_t * userlandMagicSlotBN0120 = *(uint32_t **)0x90420000;

    bool userlandMagicSlotAN0110IsValid = reverse32(0xfeedc0de) == (uint32_t)userlandMagicSlotAN0110;
    bool userlandMagicSlotBN0110IsValid = reverse32(0xfeedc0de) == (uint32_t)userlandMagicSlotBN0110;
    bool userlandMagicSlotAN0120IsValid = reverse32(0xfeedc0de) == (uint32_t)userlandMagicSlotAN0120;
    bool userlandMagicSlotBN0120IsValid = reverse32(0xfeedc0de) == (uint32_t)userlandMagicSlotBN0120;

    int N0110Counter = userlandMagicSlotAN0110IsValid + userlandMagicSlotBN0110IsValid;
    int N0120Counter = userlandMagicSlotAN0120IsValid + userlandMagicSlotBN0120IsValid;

    // At least one slot indicate N0110 and none N0120
    if ((N0110Counter > 0) && (N0120Counter == 0)) {
        return 1;
    }

    // At least one slot indicate N0120 and none N0110
    if ((N0120Counter > 0) && (N0110Counter == 0)) {
        return 2;
    }

    // We weren't able to determine the model, so we just return unknown
    return 0;
}

It will return 0 if the model is unknown, 1 for N0110 or N0115 and 2 for N0120. This method doesn't make any difference between the N0110 and N0115 as it's not required for accessing storage.

This function is available in NumWorks Extapp Storage, so you probably don't need to use this code in your app.

Slot Info

The slotInfo address depend on the calculator model (see the previous section, determining device model).

ModelAddress
N0110 and N01150x20000000
N01200x24000000

This address is the beginning of the RAM as the slot info is stored there.

The structure is very simple:

IndexSize (bytes)FormatNameUsage
0x04uint32Magic (0xBADBEEEF)Determine if the slot info is valid
0x44uint32Kernel header addressGet the start address of the kernel header
0x84uint32Userland header addressGet the start address of the userland header
0xC4uint32Magic (0xBADBEEEF)Determine if the slot info is valid

You can get the start address of the slot by doing "Kernel header address" - 0x8. For the slot A, it will return 0x90000000. For the slot B, it will return 0x90400000. For the slot Khi, it will return 0x90180000. See the table in slots / Custom firmwares for more details.

Kernel header

The kernel starts at the slot start. The Kernel Header immediately follows the first 8 bytes of the kernel, which are not part of the header itself. The address of the Kernel Header is also given in the slot info.

Here is what the first 8 bytes of the kernel contain:

Index (from kernel start)Size (bytes)FormatNameUsage
0x04uint32Null bytes
0x44uint32Size of kernel (from 0x8) + userlandUsed for signature check

The Kernel Header contains some metadata about the kernel, but unless a kernel is running a different version than the userland (shouldn't happen during normal use), the userland header will contain the same information. The only information that's only available in the kernel header is the patch level (git commit).

Index (from kernel header)Size (bytes)FormatNameUsage
0x04uint32Magic (0xF00DC0DE)Determine if the kernel header is valid
0x48stringVersionVersion number of the kernel, like 23.2.5
0xC8stringPatch levelCommit of the kernel, like cba3ef2
0x144uint32Magic (0xF00DC0DE)Determine if the kernel header is valid

Userland header

Userland header address is different between N0110, N0115 and N0120:

ModelSlot A userland headerSlot B userland header
N0110 and N01150x900100000x90410000
N01200x900200000x90420000

It's address can be retrieved in the slot info for easier processing.

It's one of the most useful header, and contain many informations:

IndexSize (bytes)FormatNameUsage
0x04uint32Magic (0xFEEDC0DE)Determine if the userland header is valid
0x48stringVersionVersion number of the userland, like 23.2.5
0xC4uint32Storage addressAddress of the Python storage inside the RAM
0x104uint32Storage sizeSize of the Python storage
0x144uint32External apps flash startStart of the zone where external apps can be flashed
0x184uint32External apps flash endEnd of the zone where external apps can be flashed
0x1C4uint32External apps ram startStart of the RAM zone where external apps can run
0x204uint32External apps ram endEnd of the RAM zone where external apps can run
0x244uint32Magic (before Epsilon 22) (0xFEEDC0DE)Determine if the userland header is valid
0x244uint32Username start (after Epsilon 22)Start of the flash zone where username is written
0x284uint32Username end (after Epsilon 22)End of the flash zone where username is written
0x3C4uint32Magic (after Epsilon 22) (0xFEEDC0DE)Determine if the userland header is valid

Username flash zone only appeared with Epsilon 22, so it wasn't in the userland header before.

On custom firmwares, the normal userland header is directly followed by Omega and Upsilon headers:

IndexSize (bytes)FormatNameUsage
0x04uint32Omega magic (0xDEADBEEF)Determine if Omega (or a fork or Omega) is installed
0x416stringOmega versionVersion number of Omega userland, like 2.0.2
0x1416stringOmega usernameUsername of the Omega installation, as ASCII string
0x244uint32Omega magic (0xDEADBEEF)Determine if Omega (or a fork or Omega) is installed
0x284uint32Upsilon magic (0x69737055)Determine if Upsilon is installed
0x3C16stringUpsilon versionVersion number of Upsilon, like 1.0.1-dev
0x4C4uint32osType (0x78718279 for Upsilon)Value that should be unique to each Upsilon fork
0x504uint32Upsilon magic (0x69737055)Determine if Upsilon is installed

The userland header is automatically parsed by Upsilon.js.

Storage structure

The storage base address and size can be found inside the userland header.

The structure of the storage is very simple:

  • At index 0x0, a magic (0xBADD0BEE) to determine if the storage is valid
  • A succession of records
  • At least one zero byte after a records
  • Another magic at the end (0xBADD0BEE)

A record is also very simple:

  • Size of the records (encoded as uint16, so 2 bytes). Adding the size to the address of the size will lead to the next record size
  • Filename, as a zero-terminated string
  • Content. Its size is determined as "size - 2 - (filename length + 1)". The -2 offset is to remove the 2 bytes used by the size itself, and the filename + 1 is to remove the filename and its zero byte at the end.

If the size of a record is 0, then the storage can be assumed as finished. However, to avoid potential corruption when manipulating storage, the space between the last record and the end of the storage should be entirely zero-filled.

The NumWorks Extapp Storage library is trying to implement this structure for external apps without needing to care about the underlying data structure. From a computer, you can use Upsilon.js.

Python scripts

Python scripts are a bit special as they need to store the automatic importation state for the console. It's implemented by making the first byte of the content a boolean. To read a Python file, you will have to shift by one the address where you are reading to get the real content.

Another particularity of Python scripts are that they are zero-terminated for easier internal processing.

Internal files

On recent Epsilon versions, files named pr.sys and gp.sys seems to hold user preferences and have to be the first files of the storage. They shouldn't be removed at the risk of crashing the calculator (reboot).

Other applications files (equations, grapher, statistics, sequences, user-defined variables in calculations, regressions and possibly other files) aren't stable between Epsilon versions, so be careful when restoring a backup of the calculator files (for example using NumWorks Connector).

Edit this page
Last Updated:
Contributors: Yaya-Cout
Prev
Userland