• Updated 2023-07-12: Hello, Guest! Welcome back, and be sure to check out this follow-up post about our outage a week or so ago.

Read/Write PRAM and xPRAM

Hello folks!

I recently bought a PowerBook Wallstreet/PDQ. Of course the PRAM battery is dead. That's nothing too bad but since I'm a software guy I thought that it should be possible to save the (x)PRAM to disk on shutdown and restore it after reboot if a PRAM reset is detected (e.g. year < 2024 or year >= 2039). So the last volume settings, display brightness, region (Date & Time) and so on are restored.

I know there are some solutions to achieve my needs but... Where's the fun part when just using them? 😄 I really want to understand things how they were done back in the days.

Btw: "PRAM-Reader" (https://macintoshgarden.org/apps/pram-reader) doesn't work at all. Restoring the saved state leads to a black screen and a complete hang.

What I've found out:
  • "Old" PRAM is 20bytes, "extended" PRAM is 236bytes (according to "Inside Macintosh, Parameter RAM Utilities", from 1994).
  • Getting the pointer for the PRAM is done by using "GetSysPPtr".
  • Changing values is done by changing them directly in memory.
  • Saving is done by using "WriteParam".
The documentation seems to work with the first 20 bytes (reading and writing works). But the extended PRAM doesn't seem to be directly after these 20 bytes. Reading 256 bytes and then writing them back causes crashes, hangs and so on.

And here comes the tricky part: I want to do everything in REALbasic! (That's the language I'm most familiar with on Mac.)

I hope someone out there has some advice for me. :)

Thank you all!
Mike
 

cheesestraws

Well-known member
it should be possible to save the (x)PRAM to disk on shutdown and restore it after reboot if a PRAM reset is detected (e.g. year < 2024 or year >= 2039). So the last volume settings, display brightness, region (Date & Time) and so on are restored.

This is totally possible, and sounds like a fun project to attempt to me :)

But the extended PRAM doesn't seem to be directly after these 20 bytes

You don't access the xPRAM like that. You have to use the (undocumented) _ReadXPRam and _WriteXPRam traps. There's loads of info on that on https://okmij.org/ftp/xPRAM.html including example code in ASM.

And here comes the tricky part: I want to do everything in REALbasic!

You're right, this is the tricky part, though not impossible :) As far as I know, REALbasic can only make applications, and by the time you're able to run applications, things aren't consulting the PRAM very much. Things like the sound volume, when in use, are used from their copies in main memory, and only written back to the PRAM when they change - they're only copied out of the xPRAM at startup. So changing the xPRAM won't change anything until the next reboot.

What makes this even more annoying is that some of the stuff out of xPRAM is loaded extremely early, before really any custom code can run. The main really annoying culprit here is the 32-bit addressing flag, which of course has to be consulted right at the start of the boot process.

So, one thing you could try is to write a little application that goes in the startup items folder and which loads the xPRAM if required, then immediately reboots: this might work. The OS will, in theory, reload the values you fiddled with from the xPRAM, and all will be sweetness and light - except that you have to wait for the computer to boot twice.

(This is the solution that I adopted for Force32, which is a little extension that forces 32-bit addressing always to be on - though that is an extension that runs early, so the double reboot is less annoying. It's also, interestingly, roughly what Apple did themselves when 32-bit addressing became mandatory: if 32-bit mode is set off, you get a double reboot the first time when the OS sets it on)
 

Phipli

Well-known member
It's also, interestingly, roughly what Apple did themselves when 32-bit addressing became mandatory: if 32-bit mode is set off, you get a double reboot the first time when the OS sets it on)
The 7.6.1 double boot frequently scares me half to death when I've been soldering on my Centris. Almost as bad as accidentally leaving 768MB of RAM in a Beige G3 and wondering why video isn't initialising.
 
This is totally possible, and sounds like a fun project to attempt to me :)
I hope that the fun part won't change to frustration... 😄

You don't access the xPRAM like that. You have to use the (undocumented) _ReadXPRam and _WriteXPRam traps. There's loads of info on that on https://okmij.org/ftp/xPRAM.html including example code in ASM.
Okay, I already stumbled over these traps... But I was hoping not to need them.
With that in mind I did some testing but I'm a little stuck. - Maybe you (or someone else) can help here...

I declared "CallUniversalProc" as follows:
Code:
Declare Function API_CallUniversalProc Lib "InterfaceLib" Alias "CallUniversalProc" (theProcPtr As Ptr, procInfo As Integer, ByRef buffer As Ptr, bufferSize AS Integer) As Integer

But I think that this isn't right due to the header definition in "MixedMode.h":
C++:
EXTERN_API_C( long )
CallUniversalProc(
  UniversalProcPtr    theProcPtr,
  ProcInfoType        procInfo,
  ...);
Do you have any idea how I declare a function in RB with dynamic parameters?

Also building of the procInfo is a bit obscure...
C++:
#define COMP_NORET_2(name, a1, a2)                                            \
    name##_procinfo = kPascalStackBased                \
        | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(a1)))    \
        | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(a2)))
#define RD_ALLOC(routine) static RoutineDescriptor            \
    routine##_RD = BUILD_ROUTINE_DESCRIPTOR(routine##_procinfo, routine)

enum {
  COMP_NORET_2(read_extended_PRAM,char *,const short)
};
I assume that "a1" is "char *" and "a2" is "const short"?
Do you know which length a "char *" (Pointer) is? - Two or four byte? My current assumption is four byte.

Currently when reading xPRAM (like I implemented it) I'm getting error 3 and my app crashes.

Thank you for your help and tips! :cool:
 
For something like this I personally wouldn't bother making anything PPC-native. It's not like the speed really matters, and obviously the trap is rather easier to call from 68k code.
Haven't thought about doing native 68k... - I gave it a try and I'm stuck at the very same problem. I want to call a trap from RB but I absolutely don't know how to do that. I created a new post for that specific question.

All pointers are four bytes.
At least this assumption was right. 😄

Anyway, thanks for the tips and thoughts!
 
I got it working! ...kind of...

The optional parameters for CallUniversalProc have to be Pointers.
And one "Declare Function" was wrong... One parameter had an extra "ByRef" and that caused the code not to execute/crash.

The saved result is now the same as in "PRAM-Reader". - And the behavior when restoring from file ist also the same. Complete crash of the PowerBook. 😞

Then I created a new project in CodeWarrior to try the code from here: https://okmij.org/ftp/xPRAM.html
Reading xPRAM works and it gives me different results than my RB code, so using these results to restore may work (I didn't test that). - But I don't have any idea what really is the difference...

Does anyone else have any idea what I could check now?
 
What do the differences look like? You can either post the two files here or use Hex fiend to have a look at them.
Image 01:
Saved file of my RB application.
The first 20 bytes are PRAM (can be ignored here) then followed by 256 bytes of xPRAM. This is the exact same data that "PRAM-Reader" gets.

Image 02:
256 bytes of the xPRAM-Call from C++ (CodeWarrior).

Image 03 - 05:
xPRAM data read out by "Pram Doctor". - That also differs from the first two screenshots.
 

Attachments

  • 01_RB_Call.jpg
    01_RB_Call.jpg
    87 KB · Views: 9
  • 02_CPP_Call.jpg
    02_CPP_Call.jpg
    53 KB · Views: 9
  • 03_PramDoctor_1.jpg
    03_PramDoctor_1.jpg
    54.8 KB · Views: 5
  • 04_PramDoctor_2.jpg
    04_PramDoctor_2.jpg
    59.8 KB · Views: 3
  • 05_PramDoctor_3.jpg
    05_PramDoctor_3.jpg
    52.7 KB · Views: 9
I'll give it up... In the last few days I tested almost every possible combination. Even ditching RB and using C/C++ instead doesn't work.
Everytime I restore the saved PRAM either nothing happens or the PowerBook freezes completely.

The big problem is that I have no application where I can say that the dumped PRAM is in fact the "real one" (for comparison to the data my code fetches and saves).

What I also found out: "Pram Doctor" is not suitable. Maybe it's way too old. Sometimes the saved PRAM data contains strings from system menus or even displayed text. So I ditched that app for comparison etc.

TechTool Lite 3.0.4 seems to be the first application to save and restore (x)PRAM values (nearly?) correctly. But not everything I thought gets restored. (For example, the display brightness won't be restored.)
The saved file is way bigger than I'd expect (566 bytes). There is a part where my dumped data equals the TT-Lite saved data (but still not all of my 256 saved bytes).

I still hope that here's someone who has an idea on how to achieve saving and restoring (x)PRAM...
 
Top