New public /Shared folder

I'll just copy what I wrote for the release notes:

Experimental feature: added read/write access for all games to top-level /Shared folder

Having a way for games to share data has been a feature request for a long time. We tried to imagine all the use cases here and thought about how we’d support them, but looking at it that way it becomes a big hairy mess. So instead we’re doing the simplest thing imaginable: adding a top-level shared folder that any game can read from and write to, with no enforced structure whatsoever, and we’ll just see what happens. My hope is that after testing this out the community comes up with a set of best practices that we then build API around--or at least put into documentation.

This is a thread for discussing the feature and figuring out how we should use this. Note the experimental feature there! We may wind up changing this in ways that break compatibility; e.g. instead of having everything right there in the top level of the folder we might decide to add subfolders for various file types (sounds, images, etc.) and require you to use those instead.

Right now there's no access control, any game can do whatever it wants with the data in the folder including deleting everything! We'll find out if this is a problem or not. We need to add a system for asking the user for various permissions, so that might help here.

Anyway, let us know if you run into any problems. We can't wait to see how this turns out!

19 Likes

This is cool! I'm nowhere near using this feature, but here's a thought.

Files could be protected with auto-generated passwords. Maybe a random sequence of 16 alphanumeric characters?

When a file is added to the /Shared folder, the function could return the file's password so that the game can access the file in the future. If the game provides a way for the user to see the password, then other games could prompt the user to enter the password to read the file. Maybe the user would also need to indicate which game and/or file to attempt accessing.

Files could also be marked as read-only.

There could be one password for reading and another for writing.

Sound a bit tool manual for me on the user side, especially since text entry is awkward on playdate.

If enforcement turns out to be needed, maybe have a password that is known by the developer, could be used. Hard to estimate whether that could be kept secure in lua compiled code but still.

Another idea is to have a system dialog that simply asks the user "Do you want to allow game A to access game B's data"?
Where that data is not in the shared folder, but in a folder with the same name as the package name, just like the current save data

1 Like

I probably missed it, but how do we access the shared folder? Couldn't find it in te 2.4.0 docs

A yes/no prompt is a lot simpler than password protection. Enforcement would be ideal if games were allowed to overwrite or delete files from other games. If shared game data is read-only, then prompting the user for access to another game might be enough.

Yeah the use cases I'm thinking of would only allow read access of another games data.

What I know works: playdate.file.listFiles("/Shared") to get a list of the files in the shared folder, playdate.file.open("/Shared/hello.txt", <mode>) to open a file for reading and writing. I assumed using a /Shared/... path would work in any function you give a file path, but it sounds like the datastore function don't like it, for one. I was going to hold off on writing docs until this thing has jelled a bit more.

3 Likes

mind is reeling out with the game design possibilities here. thanks for the update! hoping to see it jell up a bit more but unlocking content based on other owned games is top of mind.

inching closer to the playdate meta-gacha game of my dreams

Writing to another game's file could be useful to support features like trading Pokemon. Deleting another game's file seems unnecessary.

1 Like

Given this is experimental it's too early to implement anything in Pulp, but I do have ideas for what that might look like in the future (as it would be really cool for e.g. sequels carrying over data).

I am imagining something similar to the existing store, restore and toss functions, but targeting a json file in the /Shared folder with a specified name.

share

share "variableName" to "shareFilename"

or

share

Copies the current value of the variable into shared storage. Shared storage is written to disk between the exit and enter events when changing rooms or when the Player reaches an ending. When share is called on its own all values in shared storage are written to disk immediately.

receive

receive "variableName" in "shareFilename"

or

receive "shareFilename"

Sets the variable to the corresponding value in shared storage. When "variableName" is omitted all values are copied out of the specified shared file.

unshare

unshare "variableName" in "shareFilename"

or

unshare "shareFilename"

Removes the variable from shared storage. When "variableName" is omitted all values are removed from the specified shared file. When the last variable is removed from shared storage the shared file is deleted.

3 Likes

I've been messing with the shared folder in order to make a free demo of my game that lets you transfer data to the main game, and I'm pretty happy something like this exists now! I did run into an issue where you can't seem to make folders in /Shared/ through the mkdir function. When I try to use mkdir it just makes a new folder called /Shared/whatever in my game's data folder. I think it would help keep things a bit more organized if we were able to make folders in shared, and it would also help users who want to delete specific shared data without having to sort through a bunch of random files.

1 Like

Okay this is super cool. One of my favourite games of all time is Innerspace, where you fought viruses on levels determined by your own PC's files.

When I have been thinking of ways to do something similar on Playdate the lack of any kind of shared folder access has stopped me dead. If this ends up being kept and people actually use it, maybe I could make something along those lines!

I am pleased.

You know what would be cool?

If the contents of this folder could sync to the cloud somehow.

I'm thinking it could allow some type of multiplayer games. Somehow. Maybe.

If so, I think an API to only allow writing of small amounts of data would be beneficial. Not megabytes.

1 Like

Shared data is kind of cool. My inital thought is that people could weave in what has happend in another players game into their game. Im not quite sure what kind of data would be useful and how but It sounds like a fun concept.

If someone outputs data into this folder I'd love to hear about it and see if I can think of a way to incorporate it in my game.
Im hoping xml file format will be the winner here and perhaps a couple of common tags that we can read from games could be established.

While I do hope that there will be no one who would intentionally mess up shared game data, the chance is always there that it can happen and its probably hard to defend against without making it overly cumbersome to use. A simple solution is just to have a policy that all games should store its own shared data in a backup place and if the data is then missing in the shared folders copy it to the shared folder. This way even if someone does manage to corrupt some shared data each game has the ability to restore it. I think a solution like this might be the simplest and most stable one rather than passwords and a other schemes

one approach would be to steal from the web same-origin policy approach

  1. shared folder with subfolders based on reverse domain names
  2. games have a domain name attribute
  3. games can read/write from their folder, and read from anything with the same top level domain name. games and also read/write from second level domain (example.com)

to enable 1, we could require devs to host a file at the domain name, with content based on the game.

so example
game, domain, file, content
g1, g1.example.com, g1.example.com/playdate_signv1, "g1"
g2, g2.example.com, g2.example.com/playdate_signv1, "g2"

this could allow this kind of progression

  • then game g1 can RW its file (com/example/g1/data)
  • then game g2 is released can RW its file (com/example/g2/gamestuff), but also R (com/example/g1/data) as same domain.
  • author of g1 and g2 can also RW a file to (com/example/shared) for either game

maybe I over thought this...

I think naming the folder after the bundle id would be easier to maintain than a bunch of nested folders, plus it’s already available in the sdk.

We’re doing some exploring into an achievement framework on the discord and I think we roughly landed on com.dev.game/[file].json where the file would be achievements or whatever was needed.

This has the extra benefit of being super easy to support cross-game unlocks because you can isdir the game’s bundle id to check for ownership

Obviously this is ignoring permissions and stuff, I’m not worried about that :person_shrugging:

The main problem with using reverse domain names for security is the fact that you can't actually enforce them. Since this isn't the web, nothing prevents a game from fraudulently using a different development group's top level domain in order to mess with their data. Using bundleIDs has a similar problem in that not only can you technically write anything into the bundleID field it'll end up being mangled by web-based sideloading.

I don't think permissions are overly practical in any form other than a per-game all or nothing sort of system pop-up, if just because restricting read/write access to specific sub-directories neuters the usefulness of a shared root directory. You can't completely prevent possible malicious behavior without placing every game in an isolated sandbox, and then we just have game data directories again.

Basically, my thoughts right now pretty much boil down to the following code of etiquette:

  1. Use an orgname, reverse domain name, or some other perfectly unique identifier to subdivide content explicitly for your own game. Don't rely on bundleIDs, and don't be afraid to allow the multi-pdx games and/or demos /Shared enables to share the same ID.
  2. If data is being written under a known standard (such as the achievement standard we're working on), start from a root folder defined by the standard and then subdivide on identifiers (or vice-versa, it's worth discussing which).
  3. If data is being written based on a common content type (text documents, gif/pdi images, etc) and/or being provided by the user (music files, txt-based ebooks, etc), put it under a sensibly named root-level folder like /Data/Music. Like how PCs always have /Documents, /Downloads, /Music, /Pictures, /Video, or how Android devices always add folders like /Alarms or /Ringtones to the mix.
  4. If you're working with data in a common root-level folder, be extremely explicit to the user about when you're overwriting files.
  5. Back up important data generated purely by your game within your game's own data folder, just in case.
2 Likes

re: the achievements framework as Dimitri mentions, the repo is pd-achievements and there's also a shared design doc.

There was some discussion in the Playdate Squad Discord around how to organise the Shared folder as a whole and I thought I'd copy over my thoughts for posterity:

It makes sense for data folders to be game scoped because they aren't shared. Whereas if I'm a game reading from Shared, my first priority is probably looking for a tye of data rather than a specific game e.g. if I'm looking for shared music, I'd want to scope to a Music folder, rather than check every game dir for shared music.

I can also imagine things in Shared that aren't owned by any game, like user profile info where the player could put e.g. their birthday for use by any game which could go in a Profile folder. Or music added manually to the Music folder.

So I picture a structure like

Shared
|- Music
   |- An Album
      |- Song 1
      |- Song 2
      |- ...
   |- Game ID A
      |- Song 1
      |- Song 2
      |- ...
|- Achievements
   |- Game ID A
      |- achievements.json
   |- Game ID B
      |- achievements.json
|- Profile
   |- profile.json

rather than

Shared
|- Music
   |- An Album
      |- Song 1
      |- Song 2
      |- ...
|- Game ID A
   |- achievements.json
   |- Music
      |- Song 1
      |- Song 2
      |- ...
|- Game ID B
   |- achievements.json
|- Profile
   |- profile.json

On the idea of a shared profile I was thinking of user data that would be fun and/or useful to optionally declare for games to freely read and use, like:

  • Player name
  • 3 letter name for arcade style highscores
  • Birthday
  • Favourite shape
  • Accelerometer preference
  • Crank preference

Player name and short name have obvious applications. Birthday would let games do a little acknowledgement of some sort (of course you could put whatever date you wish and change it freely). "Favourite shape" came from me thinking how "favourite colour" is a kind of easy/standard thing that doesn't make sense with a 1-bit screen, so shape is a nice analog - it could be used for an icon for example. Things like accelerometer preference and crank preference would be useful when there are control scheme options so the game could default into whatever is preferred.

2 Likes

I wanted an easy way to work with the shared folder while stuff is still flexible, so I made a little script that wraps the regular file/datastore functions to work relative to the shared folder. I put it in playdate namespace because lua doesn't stop me :see_no_evil:

import "libraries/pdShared"

-- Creates /Shared/Data/com.example.mygame/ (if it doesn't exist)
local shared = playdate.file.shared("Data")
print("Shared path: " .. shared:getPath())

-- Create a json file at /Shared/Data/com.example.mygame/test.json
shared.datastore.write({foo="bar"}, "test")
-- Print out the file
printTable(shared:listFiles("."))

https://github.com/GammaGames/pdShared

Edit: Just updated it so you set the folder when you create an instance of the Shared folder

1 Like