vc64web / virtualc64web

vc64web - Commodore C64 Emulator for iPad iPhone Android and the Web with CSDb access for thousands of demos at your fingertip.
https://vc64web.github.io/doc/about.html
GNU General Public License v3.0
45 stars 5 forks source link

modal dialog to load roms via file dialog and save them to the browsers local storage #26

Closed mithrendal closed 4 years ago

mithrendal commented 4 years ago

The plan: when roms folder is empty the core will notify the javascript part that it needs roms to start ... the javascript part looks into local storage if it can find the roms there and when it does not find them then it invokes a modal dialog asking the user for help. The user can then select the rom files with the file dialog and the javascript part saves them for later use into the local storage. On subsequent visits the user has not to be bothered again as the roms are already in the browsers local storage ...

dirkwhoffmann commented 4 years ago

On subsequent visits the user has not to be bothered again as the roms are already in the browsers local storage ...

Sounds good to me. But what happens if the user wants to exchange the current Roms with new ones? He must be capable of opening the Rom dialog manually.

mithrendal commented 4 years ago

We should have additionally a roms button in the UI, right? There the user can manage its local storage.

mithrendal commented 4 years ago

to get in touch with the cores API ... I just allowed our current file drop zone to accept .bin files and only added this code to the existing D64/CRT loader


extern "C" void wasm_loadFile(char* name, Uint8 *blob, long len)
{

...
  else if (checkFileSuffix(name, ".BIN")|| checkFileSuffix(name, ".bin")) {
    printf("isBIN\n");

    bool result;
    bool wasRunnable = wrapper->c64->isRunnable();
    //ROMFile *rom = ROMFile::makeWithFile(name);
    ROMFile *rom = ROMFile::makeWithBuffer(blob, len);

    if (!rom) {
        printf("Failed to read ROM image file %s\n", name);
        return;
    }

    wrapper->c64->suspend();
    result = wrapper->c64->flash(rom);
    wrapper->c64->resume();

    if (result) {
        printf("Loaded ROM image %s.\n", name);
    } else {
        printf("Failed to flash ROM image %s.\n", name);
    }

    if (!wasRunnable && wrapper->c64->isRunnable())
        wrapper->c64->putMessage(MSG_READY_TO_RUN);

then on startup a black screen

grafik

constructing C64 ...
adding a listener to C64 message queue...
vC64 message=MSG_PAL, data=0
vC64 message=MSG_VC1541_ATTACHED_SOUND, data=1
vC64 message=MSG_VC1541_ATTACHED, data=1
vC64 message=MSG_VC1541_RED_LED_OFF, data=1
vC64 message=MSG_VC1541_MOTOR_OFF, data=1
vC64 message=MSG_VC1541_NO_DISK, data=1
vC64 message=MSG_DISK_SAVED, data=1
vC64 message=MSG_PAL, data=0
vC64 message=MSG_NO_CARTRIDGE, data=0
vC64 message=MSG_CART_SWITCH, data=0
vC64 message=MSG_IEC_BUS_IDLE, data=0
vC64 message=MSG_VC1541_ATTACHED, data=1
vC64 message=MSG_VC1541_RED_LED_OFF, data=1
vC64 message=MSG_VC1541_MOTOR_OFF, data=1
vC64 message=MSG_VC1541_NO_DISK, data=1
vC64 message=MSG_DISK_SAVED, data=1
vC64 message=MSG_VC1541_DETACHED, data=2
vC64 message=MSG_VC1541_RED_LED_OFF, data=2
vC64 message=MSG_VC1541_MOTOR_OFF, data=2
vC64 message=MSG_VC1541_NO_DISK, data=2
vC64 message=MSG_DISK_SAVED, data=2
vC64 message=MSG_VC1530_NO_TAPE, data=0
vC64 message=MSG_VC1530_PROGRESS, data=0
vC64 message=MSG_WARP_OFF, data=0
vC64 message=MSG_ALWAYS_WARP_OFF, data=0
set SID to freq= 44100
can not get hardware accelerated renderer going with software renderer instead...
Size changed: 392, 248
wrapper calls 4x c64->loadRom(...) method
wrapper calls run on c64->run() method
vC64 message=MSG_ROM_MISSING, data=0
after run ...

now I dragged the bin files one after the other into the drop zone (where normally th D64 goes into ...)...

grafik

load file=kernal.901227-03.bin len=8192
isBIN
Loaded ROM image kernal.901227-03.bin.
load file=characters.901225-01.bin len=4096
isBIN
Loaded ROM image characters.901225-01.bin.
load file=basic.901226-01.bin len=8192
isBIN
Loaded ROM image basic.901226-01.bin.
load file=1541-II.355640-01.bin len=16384
isBIN
Loaded ROM image 1541-II.355640-01.bin.
vC64 message=MSG_READY_TO_RUN, data=0

and then I just clicked the "halt" button and the "run" button and it gives us this !!! grafik

grafik

AWESOMENESS !!! 🀀🀀🀀

I mean we could just change the following lines in wasm_loadFile(char name, Uint8 blob, long len)


if (!wasRunnable && wrapper->c64->isRunnable())
        wrapper->c64->putMessage(MSG_READY_TO_RUN);

to


if (!wasRunnable && wrapper->c64->isRunnable())
        wrapper->c64->run();

and it would start automatical when the 4 files are dragged into the file drop zone ...

mithrendal commented 4 years ago

confirmed ... after dragging the four files into the drop zone vc64web starts automatically with this code

if (!wasRunnable && wrapper->c64->isRunnable())
        wrapper->c64->run();

So now I am getting weak... and need some advice ....

first I thought of a new rom manage dialog ... in which you could upload the roms or delete them

now I see a much simpler alternative plan (without ROM management dialog):

on MISSING_ROMS message and when there is no rom in local storage the user will get an alert popup which tells him to drag the four missing roms files into the drop zone ... then the core starts automatically ... and the rom files are saved to the local storage...

But what happens if the user wants to exchange the current Roms with new ones? He must be capable of opening the Rom dialog manually.

πŸ’πŸ»in this case he just drags the new roms into the drop zone ...

πŸ€” Hmm the question is which is better from user expirience aspect ? The former extra rom managment dialog with extra rom dedicated file upload area or the simple popup message which tells the user to drag the four files into the existing file drop zone ....

mithrendal commented 4 years ago

I thought about it ... maybe we can implement both ... the simple just drag that stuff into the file drop zone box ... and also additional offer a sophisticated rom dialog with extra file upload for roms? πŸ€“

dirkwhoffmann commented 4 years ago

I have trouble to give a decent advice at the moment, because I can’t image yet how this is all going to look in the long run. Overall, it’s a good strategy to add pieces incrementally and to adjust old stuff when new stuff makes it necessary (I am developing myself this way most of the time, it’s the usual agile approach).

Therefore, let me just put in some thoughts for the long term visions:

First, have a look at: https://c64emulator.111mb.de/index.php?site=pp_javascript&lang=de&group=c64

Now, have a look at this popular site we all know: https://www.netflix.com/browse

The first page is just a page (πŸ₯±) whereas the second one is an experience (😍). In my mind I imagine VirtualC64web as an experience, because otherwise, it’ll be just another emulator in a browser.

So maybe it’s best to build a visual prototype first. I did this with vAmiga and I remember your post where you’ve been looking for the Defender of The Crown intro image in memory, but it was just two fake GIFs that were flipped by a timer πŸ˜….

Just another thought: In vAmiga, I jeopardized my original idea to use Aros as the default OS, because I felt that Aros is not as stable as I thought and because it looked so different than the Amiga I remembered. For a web emulator, a (free) default OS might make sense for other reasons though (i.e., because people expect a web site to work out of the box more than they expect this from an ordinary app).

Interestingly, there is a free C64 Rom replacement written by the Mega65 team:

https://c65gs.blogspot.com/2019/05/free-and-open-source-replacement-roms.html

I was already in contact with them some time ago (I lost contact, because I was very busy at that time):

https://github.com/MEGA65/open-roms/issues/33

Still I an not sure if it makes sense to use those Roms by default, because there are Pros and Cons:

Pros:

Cons:

Those are really just my 2 cents. Yet, I am pretty unsure about the right approach at the moment. I am just clear about the goal: Making VirtualC64web an experience 😍.

mithrendal commented 4 years ago

I have trouble to give a decent advice at the moment, because I can’t image yet how this is all going to look in the long run.

so am I 😬... currently we have three unclear front lines ...

  1. is the choosen underlying technical framework (bootstrap/jquery) sufficient enough for the greatest experience we can get from a browser ... ?
  2. I don't know how vc64web will look like when it is finally done
  3. will the local storage technic offer us really the solution of storing the roms ... a bit more permanently which I hope for ... (I just learned it just accepts string values πŸ™„ ... but we could always encode binary data to a base64 string I think 😎...)

I think in this situation the best is as you said ... the incremental approach ... small steps 🚢🏻 ... but without hesitating to throw already achieved things over board in favour of making VirtualC64web an experience. 😎

So I first will do the local storage baby ... lets see if we can save to it and restore from it... After that I will do the extra rom dialog like you did it in the MacOS VirtualC64.app ...

dirkwhoffmann commented 4 years ago
  1. is the choosen underlying technical framework (bootstrap/jquery) sufficient enough for the greatest experience we can get from a browser ... ?

Good question πŸ€”. Unfortunately, my knowledge about web technology is kind of limited.

  1. I don't know how vc64web will look like when it is finally done

It'll be great 😍.

  1. but we could always encode binary data to a base64 string I think 😎...

There might be a limitation to string size. If not, yeah, convert the Rom to a string πŸ˜‚, it's 2020.

Anyway, I'm a little anxious right now. Not because of VirtualC64web, because of Moira. I just discovered that I need to increase the program counter at the end of an instruction (during prefetch) and not at the beginning (otherwise, some exception stack frames will be wrong).

This is bad, because it's like removing the lowest brick from this tower 😬.

Bildschirmfoto 2020-05-31 um 13 31 04

Soon everything will be in ruins πŸ™ˆ.

mithrendal commented 4 years ago

Soon everything will be in ruins πŸ™ˆ.

The worst case I can think of is that when someone hits the reset button of vc64web that it then resets/deletes the internet instead πŸ™ˆ Could that be possible 😬?

We should implement an extra big red button and name it β€žnever push thisβ€œ ... do you think they will push it?

mithrendal commented 4 years ago

short update ... local storage idea seems to work

            localStorage.setItem('file1', ToBase64(byteArray));
            var restoredbytearray = Uint8Array.from(FromBase64(localStorage.getItem('file1')));

            wasm_loadfile(file.name, restoredbytearray, restoredbytearray.byteLength);

and I can see the file 😍 in the firefox webdev tools -> web storage

grafik

and all files have been loaded this way into the core which then started up immediatelyπŸ™ƒ

mithrendal commented 4 years ago

small steps ....

the file dialog detects now the four rom files. If it detects a rom file it automatically stores it into browsers local storage... Yes you can simply replace them by simply loading another rom file into it 😍

here you see firefox after cold start ... and a visit to VirtualC64web ... the roms are automatically loaded from local storage 😎...I bet no other web emulator did this before ...

grafik

How long does the local storage persist days weeks months years ... I don’t know ...

pushed this version to branch dev

Now we can test whether it works in all modern browsers...

try out here... https://mithrendal.github.io/virtualc64web/vC64.html

mithrendal commented 4 years ago

Just opened the link on my iPhone and saved the website on its homescreen.😍 of course on the first homesceen page. loaded the roms into its local storage and vc64 started up.

Next cold started safari. And on click on the vc64 icon on iPhones homescreen πŸ“² vC64web starts up with roms from local storage 😎

Turned out that local storage was a good choice. πŸ‘πŸ»Maybe we should use that technique for also storing everything we throw at the file dialog e.g. D64 files or .crt files too ?? And offer an app internal netflix like file browser which browses over the local storage 🀀

dirkwhoffmann commented 4 years ago

try out here... https://mithrendal.github.io/virtualc64web/vC64.html

I tried in Chrome and Safari and dragged in all four Roms, but the screen remains black: 🀭

Bildschirmfoto 2020-06-01 um 11 42 22
mithrendal commented 4 years ago

what did the log say ? (the text area ...) Are the roms being detected when you drag thm into the blue bordered box?

also the roms do have to end with .bin

I tried already safari mac πŸ‘Œ safari ios πŸ‘Œ firefox mac πŸ‘Œ

all working nicely

dirkwhoffmann commented 4 years ago

also the roms do have to end with .bin

Oh, why is that? 😳

Adding a .bin suffix did the trick 😎

Bildschirmfoto 2020-06-01 um 12 06 21
mithrendal commented 4 years ago

Oh, why is that? 😳

true ... we will change that ... the reason was that I compare the suffix to know what kind of archive I have to build in wasm_loadFile()

extern "C" const char* wasm_loadFile(char* name, Uint8 *blob, long len)
{
  printf("load file=%s len=%ld\n", name, len);
  filename=name;
  if(wrapper == NULL)
  {
    return "";
  }
  if (checkFileSuffix(name, ".D64") || checkFileSuffix(name, ".d64")) {
    printf("isD64\n");
    changeDisk(D64File::makeWithBuffer(blob, len),8);
  }
  else if (checkFileSuffix(name, ".G64") || checkFileSuffix(name, ".g64")) {
    printf("isG64\n");
    changeDisk(G64File::makeWithBuffer(blob, len),8);
  }
  else if (checkFileSuffix(name, ".PRG") || checkFileSuffix(name, ".prg")) {
    printf("isPRG\n");
    wrapper->c64->flash(PRGFile::makeWithBuffer(blob, len),0);
  }
  else if (checkFileSuffix(name, ".CRT")|| checkFileSuffix(name, ".crt")) {
    printf("isCRT\n");
    wrapper->c64->expansionport.attachCartridge( Cartridge::makeWithCRTFile(wrapper->c64,(CRTFile::makeWithBuffer(blob, len))));
    wrapper->c64->reset();
  }
  else if (checkFileSuffix(name, ".BIN")|| checkFileSuffix(name, ".bin")) {
    printf("isBIN\n");
    //wrapper->c64->flash(ROMFile::makeWithBuffer(blob, len),0);

    bool result;
    bool wasRunnable = wrapper->c64->isRunnable();
    //ROMFile *rom = ROMFile::makeWithFile(name);
    ROMFile *rom = ROMFile::makeWithBuffer(blob, len);

    if (!rom) {
        printf("Failed to read ROM image file %s\n", name);
        return "";
    }

    wrapper->c64->suspend();
    result = wrapper->c64->flash(rom);
    wrapper->c64->resume();

    if (result) {
        printf("Loaded ROM image %s.\n", name);
    } else {
        printf("Failed to flash ROM image %s.\n", name);
    }

    if (!wasRunnable && wrapper->c64->isRunnable())
    {
        wrapper->c64->putMessage(MSG_READY_TO_RUN);
    }

    const char *rom_type="";
    if(rom->isKernalRomBuffer(blob, len))
    {
      rom_type = "kernal_rom";
    }
    else if(rom->isVC1541RomBuffer(blob, len))
    {
      rom_type = "vc1541_rom";
    }
    else if(rom->isCharRomBuffer(blob, len))
    {
      rom_type = "char_rom";
    }
    else if(rom->isBasicRomBuffer(blob, len))
    {
      rom_type = "basic_rom";
    }
    printf("detected rom_type=%s.\n", rom_type);

    delete rom;
    return rom_type;    
  }
  return "";
}

we can impove it and make it independend of the suffix right ?

Adding a .bin suffix did the trick 😎

Is that picture chrome?

dirkwhoffmann commented 4 years ago

You shouldn't need the custom Rom handling code at all. πŸ€”

Just call C64::loadRom(const char *filename) before doing the suffix checks. If it's a Rom, VirtualC64 will auto-detect its type, install it and return true. If it's not a Rom, it'll return false.

The MSG_READY_TO_RUN message will be sent from inside loadRom once all Roms are in place.

mithrendal commented 4 years ago

C64::loadRom(const char *filename) Is for files only. Local storage is no filesystem. the rom will be pushed into the core as a byte array from the JavaScript side which in turn loads and decodes it from the local storage... all control is on the JavaScript side even the core message listener now forwards to JavaScript message_handler(). When the core reports ready to run then the JavaScript side decides to run()

Also storing to local storage happens on JavaScript side.

Do you remember we decided for the design to keep the wrapper as thin as possible... all control in HTML5

The command sequence is as follows

Mainsdl.cpp tries to load the embedded romfiles.

When there are none the core emits missing rom message

The JavaScript message handler receives it and tries to locate the rons inside the local storage.

When it does not find anything-> black screen

When it finds them it decodes them base64 and pushes them into the core with wasm_loadFile

When all four roms are pushed then wasm_loadfile emits readytorun

The JavaScript message handler receives it and calls wasm_run

dirkwhoffmann commented 4 years ago

C64::loadRom(const char *filename) Is for files only.

You are so right πŸ™„.

if (checkFileSuffix(name, ".BIN")|| checkFileSuffix(name, ".bin")) {

But you could just skip this test, couldn't you? If the file is nothing else, assume it's a Rom. If it's not a Rom,

ROMFile *rom = ROMFile::makeWithBuffer(blob, len);

will return NULL.

mithrendal commented 4 years ago

If the file is nothing else, assume it's a Rom.

Yes we will do ...😎

dirkwhoffmann commented 4 years ago

Just opened the link on my iPhone and saved the website on its homescreen.

Just did the same. On the first page. Sure thing.

loaded the roms into its local storage and vc64 started up.

How do I do that on my iPhone 😟?

mithrendal commented 4 years ago

How do I do that on my iPhone 😟?

Touch the dropzone ...

dirkwhoffmann commented 4 years ago

Touch the dropzone ...

I'm scared πŸ™ˆ

OK, if this is the only way, I'll do it...

Woaaa, I can select files. So futuristic.

Yeeees, it worked 😎

IMG_3170

mithrendal commented 4 years ago

When connecting a bluetooth keyboard you can play ...πŸ˜ŽπŸ‘πŸ»

Possibly a bluetooth xbox one controller or ps4 controller works too ... but I have not one 🀀

dirkwhoffmann commented 4 years ago

Works on the iPad, too. And I already discovered some bugs πŸ˜ƒ:

mithrendal commented 4 years ago

T64 is not implemented in the wrapper yet... D64 is implemented ...but somehow the file dialog in iOS wont let you select it ... works in Safari on a Mac so I think it is a restriction in iOS ... have to investigate ...

When I hit 'Z', VC64 displays 'Y' πŸ™Š

a bug will have to correct it in vC64keymap.js ...

mithrendal commented 4 years ago

iOS file selection repaired ...

before πŸ‘Ž

          <input id="filedialog" name="theFileDialog" type="file" accept=".g64,.d64,.crt,.prg,.bin" style="display:none" >

now πŸ‘πŸΌ

        <!-- iOS won't work with accept=".g64,.d64,.crt,.prg,.bin" on d64 files -->
          <input id="filedialog" name="theFileDialog" type="file" style="display:none" >

strange thing ...😳

now it accepts everything ... also pictures from your camera roll 😢 ... πŸ™„

maybe they repair it in iOS14 ?

I published the working version in gh-pages 😎 I like the workflow ...