[Q] running a prg from C (or asm)

Basic and Machine Language

Moderator: Moderators

pallas
Vic 20 Devotee
Posts: 212
Joined: Mon Dec 24, 2012 2:38 am

[Q] running a prg from C (or asm)

Post by pallas »

Hello,

for my upcoming shell, I need to make a routine to run a prg file from my C code.
i have taken a couple lines of code from cbm filebrowser which does:

JSR $C533 ; Relinks BASIC Program from and to any address...
JMP $C7AE ; BASIC Warm Start (RUN)

so I did:

if (cbm_load(cmd, 8, NULL) > 0) asm("\tjsr 50483\n\tjmp 51118");

but it crashes (cpu jam).
any clues?

thanks!

EDIT: maybe I'm overwriting myself? :)
groepaz
Vic 20 Scientist
Posts: 1180
Joined: Wed Aug 25, 2010 5:30 pm

Post by groepaz »

you probably have to do an ioreset and restore a bunch of things in zeropage (and pages 2/3) before loading/running a basic program works.
pallas
Vic 20 Devotee
Posts: 212
Joined: Mon Dec 24, 2012 2:38 am

Post by pallas »

Thanks.
Probably, I also need to relocate the loading code somewhere it's not touched by the load call itself (like the cassette buffer?), and run it from there.
I fear it's not trivial to do it in C and I should start looking at asm code closer :)
pallas
Vic 20 Devotee
Posts: 212
Joined: Mon Dec 24, 2012 2:38 am

Post by pallas »

I've gone a bit forward but still have problems.

This is the current code:

char* str = "\"ciao.prg\",8"; // sample loading basic code

POKE(4610, 1); // ?
POKE(4613, 156); // clr
POKE(4614, 58); // :
POKE(4615, 147); // load
memcpy((void*)4616, str, strlen(str));
memset((void*)(4616 + strlen(str)), '\0', 3); // prg end
asm("jsr 50483"); // relink basic code
POKE(45,PEEK(34)); // set free ram
POKE(46,PEEK(35));
asm("jmp 51118"); // run

Running this displays a syntax error, but if you enter "run" again, it works.
I suppose there is something about basic which should be cleaned, maybe something related to error routines?
Any clues?
User avatar
TLovskog
Vic 20 Enthusiast
Posts: 194
Joined: Fri Mar 25, 2011 3:16 pm
Location: Kävlinge, Sweden

Post by TLovskog »

JMP $C7AE ; BASIC Warm Start (RUN)

Isn't axctually the run command, it is the execution loop. The first thing I think is the problem here is that the pointer for the current BASIC text/token/data is not initialized.

"RUN" does a lot of other stuff. It initializes the above pointer, clears memory (and set relevant z-page pointers), restores DATA statement pointers, etc, etc.

Maybe ... /(And below is basically what RUN does)

;First turn of control messages and kernal messages
LDA #$00
JSR $FF90

;Reset all BASIC pointers
JSR $C659

; Continue
JMP $C7AE
BR
Thomas Lövskog
pallas
Vic 20 Devotee
Posts: 212
Joined: Mon Dec 24, 2012 2:38 am

Post by pallas »

Thanks for the information.

This is the new version:

int p = 4613;

POKE(p++, 147); // load
POKE(p++, 34); // "
memcpy((void*)p, cmd, strlen(cmd));
p += strlen(cmd);
POKE(p++, 34); // "
POKE(p++, 44); // ,
POKE(p++, 56); // 8
POKE(p++, 0); POKEW(p, 0); // prg end

POKE(45,PEEK(34)); // set free ram
POKE(46,PEEK(35));
asm("lda #00; jsr 65424"); // turn off control messages and kernal messages (looks like it has no effect)
asm("jsr 50777"); // clr
asm("jsr 50483"); // relink basic code
asm("jmp 51118"); // run

the "clr" put in the right place (asm instead of basic) made the difference.

now it seems to work for some programs but not for others:

- quickman8k: works
- tetwels8k: starts up but then the basic code gets corrupted and fails randomly
- tetwels compiled: totally garbled
pallas
Vic 20 Devotee
Posts: 212
Joined: Mon Dec 24, 2012 2:38 am

Post by pallas »

after loading the program in ram:

asm("jsr $e518 ; Initialize I/O");
asm("jsr 58459"); // initialize vectors
asm("jsr 58276"); // initialize basic ram
asm("jsr 50777"); // clr
asm("jsr 50483"); // relink basic code
asm("jmp 51118"); // run

works but it doesn't set the free ram properly.
but the same happens if you type this program:

10 load "some.prg",8

it works, but the free ram will not be set correctly, and so as soon as the program writes a variable, il will corrupt itself.

it looks like the problem is in the basic code, not c or asm.
I'm sure there are basic gurus here who can help :)

Maybe the right way is using the keyboard buffer? But will it fit the whole load command?
pallas
Vic 20 Devotee
Posts: 212
Joined: Mon Dec 24, 2012 2:38 am

Post by pallas »

ok this seems to be working for all files (except .p00):

POKE(45, size % 256); // set free ram
POKE(46, size / 256);

where "size" is file size + 4607
this is done after writing the program to memory (before "initialize I/O")
User avatar
TLovskog
Vic 20 Enthusiast
Posts: 194
Joined: Fri Mar 25, 2011 3:16 pm
Location: Kävlinge, Sweden

Post by TLovskog »

pallas wrote:works but it doesn't set the free ram properly.
but the same happens if you type this program:

10 load "some.prg",8

it works, but the free ram will not be set correctly, and so as soon as the program writes a variable, il will corrupt itself.
That is actually not so strange that it doesn't work. After you have loaded the program you restore all the pointers with ...

asm("jsr 58276"); // initialize basic ram

BASIC will then not see that there is any program loaded. First variable will then write over everything ...

asm("jsr $e518 ; Initialize I/O");
asm("jsr 58459"); // initialize vectors
asm("jsr 58276"); // initialize basic ram

That shouldn't be needed if nothing has seriously been screwed up.

So. Why do you have the same problem with the "10 load "some.prg",8

Well that is a "feature" with the BASIC. It will simply not change the end of BASIC pointer if you use the load command in another program. The end of BASIC is only updated when you type the load command interactively.

The idea here might be that if the newly loaded program is less in size than the first, all variables can be kept between loads.

If you use $FFD5 to load the new program in memory, then it will return i X/Y the highest adress used. Store these in $2D/$2E with

STX $2D ; set start of variables low byte
STY $2E ; set start of variables high byte

The do the clr etc.

Although I have started to muck around the VIC again for my GCart project, it is 30+ years since I dug around the BASIC this much ...
BR
Thomas Lövskog
User avatar
TLovskog
Vic 20 Enthusiast
Posts: 194
Joined: Fri Mar 25, 2011 3:16 pm
Location: Kävlinge, Sweden

Post by TLovskog »

Maybe ...

LDA #$... ; What is needed
LDX #$... ; What is needed
LDY #$... ; What is needed
JSR $FFBA ; Set device etc.

LDA #$LENGTH NAME
LDX #$<Name
LDY #$>NAME
JSR $FFBD ; Set name

LDX $2B
LDY $2C ; Load to BASIC Start
JSR $FFD5 ; Load
BCS ERROR

JSR $FFB7 ; Check i/o status
AND #$BF
BNE ERROR

STX $2D
STY $2C ; Store end of BASIC

JSR $C659 ; Clear and set pointers

JSR $C533 ; Rebuild chain

JMP $C7AE ; Run


Just from the top of my head, so ....
BR
Thomas Lövskog
pallas
Vic 20 Devotee
Posts: 212
Joined: Mon Dec 24, 2012 2:38 am

Post by pallas »

Thanks for the information, now it's a bit clearer.
Unfortunately I can't load the program directly because I will overwrite myself.
So I create a small program in ram which loads the program.
The solution in my previous post seems to work (it sets the "start of variables" pointer by itself).
The initialize* routines seem to be necessary, probably because cc65 does some things which you usually don't do on an asm program (so more cleaning needed).
I've also tried calling the "_exit" routing of cc65, which does the cleanup when you exit() or return from main(), but with no luck.
User avatar
Mike
Herr VC
Posts: 4816
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Post by Mike »

TLovskog wrote:Maybe ... [...]
Pretty much the same code I use in my boot loaders, just I don't bother catching I/O errors:

Code: Select all

 LDA #1
 LDX $BA           ; last device used
 LDY #0            ; secondary address = 0, force load to address given in X/Y on KERNAL load
 JSR $FFBA         ; SETLFS

 LDA #NameEnd-Name
 LDX #Name MOD 256
 LDY #Name DIV 256
 JSR $FFBD         ; SETNAM

 LDA #0            ; load (==0), not verify (!=0)
 LDX $2B
 LDY $2C
 JSR $FFD5         ; call KERNAL load routine
 STX $2D           ; set start of variables (a.k.a. "end of program") from X/Y
 STY $2E

 JSR $C533         ; relink BASIC program
 JSR $C659         ; reset TXTPTR and clear variables
 JMP $C7AE         ; enter interpreter loop (a.k.a. "RUN")

.Name
 EQUS "FILENAME"
.NameEnd
Note the routine at $C659 also purges the stack: it pops the return address off stack (to A and Y), resets the stack pointer to $FA (+ $0100, of course), and finally pushes the return address again on stack to return via RTS. All 'stack frames' above the current thread are lost.
pallas
Vic 20 Devotee
Posts: 212
Joined: Mon Dec 24, 2012 2:38 am

Post by pallas »

My solution, which worked on the vic20, didn't work for some reason on the c64, even though I've checked the addresses multiple times.

So I came up with a much simpler solution (based on the idea by Mike) with the keyboard buffer:

#if defined(__C128__)
#define KEYBUF_ADDR 842
#define KEYBUF_NUMB 208
#else
#define KEYBUF_ADDR 631
#define KEYBUF_NUMB 198
#endif

// 8E switches character set to 1 (default uppercase)
printf("\x8Eload\"%s\",8", cmd);
memcpy((void*)KEYBUF_ADDR, "\x91\x91\nrun\n", 7);
POKE(KEYBUF_NUMB, 7);
exit(0);
User avatar
Mike
Herr VC
Posts: 4816
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Post by Mike »

pallas wrote:My solution, which worked on the vic20, didn't work for some reason on the c64, even though I've checked the addresses multiple times.
Would be quite interesting to know what you think went wrong. Could you provide this earlier solution here for an overview?
So I came up with a much simpler solution (based on the idea by Mike) with the keyboard buffer:
Well, printing out appropriate statements on screen and then executing these commands from stored keypresses in the keyboard buffer surely isn't something I invented first, but I still have some suggestions:

Code: Select all

/* 8E switches character set to 1 (default uppercase) */
printf("\x8Eload"%s",8", cmd);
Doubtful, whether \x8E is really necessary, but the code could be made more flexible by making the device number dynamic:

Code: Select all

printf("\x93\x8Eload"%s",%d\n",cmd,PEEK(186));
This line also clears the screen (if you're writing out the load command, any 'design' of the loading screen is futile, anyway) and it adds a newline at the end to ensure the output buffer is flushed. PEEK(186) works for VIC-20, C64 and C128.

Code: Select all

memcpy((void*)KEYBUF_ADDR, "\x91\x91\nrun\n", 7);
POKE(KEYBUF_NUMB,7);
In this context, '\n' is not portable - some architectures identify it with ASCII 10, others with ASCII 13, sometimes even ASCII 10 + ASCII 13. It's better here to write what you want, \x0D.

Together with the cleared screen, this gives:

Code: Select all

memcpy((void *)KEYBUF_ADDR, "\x13\x0Drun\x0D", 6);
POKE(KEYBUF_NUMB,6);
... which first homes the cursor, executes the LOAD command, then types "RUN", and executes this one as well (On the C128, code 131 wouldn't work, as it doesn't expand to LOAD + RUN, but DLOAD"*" + RUN instead, which doesn't work together with the printed LOAD statement :()
pallas
Vic 20 Devotee
Posts: 212
Joined: Mon Dec 24, 2012 2:38 am

Post by pallas »

Thanks Mike for the suggestions.

Here is the other routine:

Code: Select all

	#if defined(__VIC20__)
		unsigned int p = 4613, size = 4607;
	#elif defined(__C64__)
		unsigned int p = 2053, size = 2047;
	#endif

	int fp = open(cmd, O_RDONLY), n;
	char buf[BUF_SIZE];

	if (fp <= 0) {
		printf("Can't run prg\n");
		return;
	} else {
		while ((n = read(fp, buf, sizeof(buf))) > 0) size += n;
		close(fp);
	}

	getcwd(buf, BUF_SIZE);
	POKEW(p, 147 + 34 * 256);	// load + "
	p += 2;
	memcpy((void*)p, cmd, strlen(cmd));
	p += strlen(cmd);
	POKEW(p, 34 + 44 * 256);		// " + ,
	p += 2;
	memcpy((void*)p, buf, strlen(buf));
	p += strlen(buf);
	POKE(p++, 0); POKEW(p, 0);	// prg end

	//sprintf((char*)4613, "\x93\"%s\",%s\0\0\0", cmd, buf);  bigger!

	POKEW(45, size);	// set free ram

	#if defined(__VIC20__)
		asm("jsr $E518");		// Initialize I/O
		asm("jsr $E45B");		// initialize vectors
		asm("jsr $E3A4");		// initialize basic ram
		asm("jsr $C659");		// clr + reset stack
		asm("jsr $C533");		// relink basic code
		asm("jmp $C7AE");		// run
	#elif defined(__C64__)
		//POKEW(47, size);  optional
		//POKEW(49, size);  optional
		asm("jsr $E518");		// Initialize I/O
		asm("jsr $E453");		// initialize vectors
		asm("jsr $E3BF");		// initialize basic ram

		// currently doesn't work: looks like it crashes before doing the relink

		asm("jsr $A659");		// clr + reset stack
		asm("jsr $A533");		// relink basic code
		asm("jmp $A7AE");		// run
	#endif
works on vic-20 but crashes (like if you run/stop+restore) on c64.
Post Reply