Skip to content

[Bug] States uploaded via API do not match screenshots and state files. #3031

@amn-96

Description

@amn-96

RomM version
4.6.1

Describe the bug
When using the RomM API to PUT or POST save states, screenshotFile's that I send with the stateFile's in the same request do not match each other in RomM's database.

As an aside, the save and state PUT/POST redocs don't appear to be comprehensively documented. The fields for the actual file uploads are not included in the redoc -- I had to check the endpoint source code for saves and states and base my API requests on that.

To Reproduce

Upload a series of state files for one game, with both StateFile and screenshotFile included in the request. This is the code I use to set up the API call (python):

with open(local_filepath, 'rb') as state_f:
            files = {'stateFile': (local_filepath.name, state_f)}

            if screenshot:
                screenshot_f = open(screenshot, 'rb')
                files['screenshotFile'] = (screenshot.name, screenshot_f)
                logger.debug(f"Adding screenshot to upload: {screenshot.name} (path: {screenshot})")
            else:
                screenshot_f = None
                logger.debug(f"No screenshot to upload for state: {local_filepath.name}")

            try:
                url = f"{self.romm_user.url}/api/states/"
                logger.debug(f"Uploading state: {local_filepath.name}")
                response = requests.post(
                    url,
                    auth=HTTPBasicAuth(self.romm_user.user, self.romm_user.password),
                    params={"rom_id": rom_id, 'emulator': emulator},
                    files=files
                )
                logger.debug(f"POST {url} - {response.status_code}")
                return response.json()
            finally:
                if screenshot_f:
                    screenshot_f.close()

The state files I'm dealing with are from Retroarch so they're named something like {game}.state1 with the screenshot file {game}.state1.png. I've confirmed that the screenshot files and state files DO make it to the RomM server, that each state (state1, state2, etc..) is uploaded via PUT or POST with its corresponding screenshot (state1.png, state2.png, etc...), and that the states and screenshots are associated with the correct game by inspecting the bind mounted file system and checking the frontend.

As shown below, each state file uses the same screenshot.
Image

On the server, there is a unique screenshot for each state:

Image

When I retrieve the state via the API, I can see from the response that every state file has the same screenshot file attached to it in the response. It seems to pick one at random. The example below has the "state.auto.png" file while the screenshot above uses state2 (I think). This is a partial response from GET states:

{'id': 106,
  'rom_id': 267,
  'user_id': 1,
  'file_name': 'Pokemon Lazarus.state3',
  'file_name_no_tags': 'Pokemon Lazarus',
  'file_name_no_ext': 'Pokemon Lazarus',
  'file_extension': 'state3',
  'file_path': 'users/557365723a31/states/gba/267',
  'file_size_bytes': 56138,
  'full_path': 'users/557365723a31/states/gba/267/Pokemon Lazarus.state3',
  'download_path': '/api/raw/assets/users/557365723a31/states/gba/267/Pokemon Lazarus.state3?timestamp=2026-02-16 18:26:16',
  'missing_from_fs': False,
  'created_at': '2026-02-14T20:07:55+00:00',
  'updated_at': '2026-02-16T18:26:16+00:00',
  'emulator': None,
  'screenshot': {'id': 105,
   'rom_id': 267,
   'user_id': 1,
   'file_name': 'Pokemon Lazarus.state.auto.png',
   'file_name_no_tags': 'Pokemon Lazarus',
   'file_name_no_ext': 'Pokemon Lazarus',
   'file_extension': 'state.auto.png',
   'file_path': 'users/557365723a31/screenshots/gba/267',
   'file_size_bytes': 7899,
   'full_path': 'users/557365723a31/screenshots/gba/267/Pokemon Lazarus.state.auto.png',
   'download_path': '/api/raw/assets/users/557365723a31/screenshots/gba/267/Pokemon Lazarus.state.auto.png?timestamp=2026-02-16 18:26:16',
   'missing_from_fs': False,
   'created_at': '2026-02-14T20:07:55+00:00',
   'updated_at': '2026-02-16T18:26:16+00:00'}},
 {'id': 107,
  'rom_id': 267,
  'user_id': 1,
  'file_name': 'Pokemon Lazarus.state.auto',
  'file_name_no_tags': 'Pokemon Lazarus',
  'file_name_no_ext': 'Pokemon Lazarus',
  'file_extension': 'state.auto',
  'file_path': 'users/557365723a31/states/gba/267',
  'file_size_bytes': 426048,
  'full_path': 'users/557365723a31/states/gba/267/Pokemon Lazarus.state.auto',
  'download_path': '/api/raw/assets/users/557365723a31/states/gba/267/Pokemon Lazarus.state.auto?timestamp=2026-02-16 18:26:16',
  'missing_from_fs': False,
  'created_at': '2026-02-14T20:07:55+00:00',
  'updated_at': '2026-02-16T18:26:16+00:00',
  'emulator': None,
  'screenshot': {'id': 105,
   'rom_id': 267,
   'user_id': 1,
   'file_name': 'Pokemon Lazarus.state.auto.png',
   'file_name_no_tags': 'Pokemon Lazarus',
   'file_name_no_ext': 'Pokemon Lazarus',
   'file_extension': 'state.auto.png',
   'file_path': 'users/557365723a31/screenshots/gba/267',
   'file_size_bytes': 7899,
   'full_path': 'users/557365723a31/screenshots/gba/267/Pokemon Lazarus.state.auto.png',
   'download_path': '/api/raw/assets/users/557365723a31/screenshots/gba/267/Pokemon Lazarus.state.auto.png?timestamp=2026-02-16 18:26:16',
   'missing_from_fs': False,
   'created_at': '2026-02-14T20:07:55+00:00',
   'updated_at': '2026-02-16T18:26:16+00:00'}},

Expected behavior
I'm new to using python's requests so it may be that I'm misunderstanding how the request works, but since the stateFile and screenshotFile are in the same request, I'd expect that the screenshotFile is automatically associated with the stateFile so that retrieving a state file retrieves the corresponding screenshot and the frontend shows that corresponding screenshot as well.

I assume this behavior would apply to save files (.srm) as well, but I haven't tried that.

Happy to upload a test set of states and screenshots if you would like.

Screenshots
(above)

Server (please complete the following information):

  • OS: Ubuntu 24.04. RomM is running via Docker.
  • RomM Version 4.6.1

Client (please complete the following information):

Browser to check the available state files and the screenshots shown on them; direct API request via Python for the functionality.

Additional context
I ran into this writing a small tool to push my syncthing save backups to RomM. (https://github.com/amn-96/romm_syncthing_sync). I wanted to be able to use the same saves and states across EmulatorJS, Grout, and any other device.

Thanks for any help!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions