Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workshop mods are broken in HL2 #98

Closed
Edremon opened this issue Nov 25, 2024 · 17 comments
Closed

Workshop mods are broken in HL2 #98

Edremon opened this issue Nov 25, 2024 · 17 comments

Comments

@Edremon
Copy link
Contributor

Edremon commented Nov 25, 2024

I'm trying to add mods to HL2, in the current emulator state, the mod doesn't load correctly and show as "Broken Item!" in the workshop menu, the preview image is also not shown.

image

In HL2 logs, we get [CWorkshopImage] GetUGCDetails failed? (UGC=0000000000000004 nFileSizeInBytes=-1). this function wasn't implemented, I implemented it like so:

// Gets metadata for a file after it has been downloaded. This is the same metadata given in the RemoteStorageDownloadUGCResult_t call result
bool Steam_Remote_Storage::GetUGCDetails( UGCHandle_t hContent, AppId_t *pnappid, STEAM_OUT_STRING() char **ppchName, int32 *pnFileSizeInBytes, STEAM_OUT_STRUCT() CSteamID *pSteamIDOwner )
{
    PRINT_DEBUG("%llu", hContent);
    std::lock_guard<std::recursive_mutex> lock(global_mutex);
    if (hContent == k_UGCHandleInvalid) return k_uAPICallInvalid;

    if (auto query_res = ugc_bridge->get_ugc_query_result(hContent)) {
        auto mod = settings->getMod(query_res.value().mod_id);
        auto &mod_name = query_res.value().is_primary_file
            ? mod.primaryFileName
            : mod.previewFileName;
        int32 mod_size = query_res.value().is_primary_file
            ? mod.primaryFileSize
            : mod.previewFileSize;

        *ppchName = new char[mod_name.size() + 1];
        std::strcpy(*ppchName, mod_name.c_str());
        *pnFileSizeInBytes = mod_size;
        *pSteamIDOwner = mod.steamIDOwner;

        return true;
    }

    return false;
}

Preview image is now shown, but I still get "Broken Item!" and mod is not working.

image

I tried multiple mods, all have the same problem. Here mods.json on that example:

{
    "3366485326": {
        "title": "GoldSource GUI [HL2]",
        "primary_filename": "workshop_dir.vpk",
        "preview_filename": "preview_image.jpg"
    }
}

Mods directory structure:

mods
└── 3366485326
    ├── workshop_000.vpk
    └── workshop_dir.vpk

I tried filling all the json data, debug have nothing interesting in it, below are all related lines to workshop or UGC.

Logs
[1 ms, 1617 us] [tid 359792] bool Local_Storage::load_json(const std::string&, nlohmann::json_abi_v3_11_3::json&) Loaded json '/Half-Life 2/bin/steam_settings/mods.json' (1 items)
[1 ms, 1621 us] [tid 359792] void parse_mods_folder(Settings*, Settings*, Local_Storage*) Attempting to parse mods.json
[1 ms, 1729 us] [tid 359792] void try_parse_mods_file(Settings*, Settings*, nlohmann::json_abi_v3_11_3::json&, const std::string&)   parsed mod '3366485326':
[1 ms, 1733 us] [tid 359792] void try_parse_mods_file(Settings*, Settings*, nlohmann::json_abi_v3_11_3::json&, const std::string&)     path (will be used for primary file): '/Half-Life 2/bin/steam_settings/mods/3366485326'
[1 ms, 1735 us] [tid 359792] void try_parse_mods_file(Settings*, Settings*, nlohmann::json_abi_v3_11_3::json&, const std::string&)     images path (will be used for preview file): '/Half-Life 2/bin/steam_settings/mod_images/3366485326'
[1 ms, 1740 us] [tid 359792] void try_parse_mods_file(Settings*, Settings*, nlohmann::json_abi_v3_11_3::json&, const std::string&)     primary_filename: 'workshop_dir.vpk'
[1 ms, 1741 us] [tid 359792] void try_parse_mods_file(Settings*, Settings*, nlohmann::json_abi_v3_11_3::json&, const std::string&)     primary_filesize: 570 bytes
[1 ms, 1743 us] [tid 359792] void try_parse_mods_file(Settings*, Settings*, nlohmann::json_abi_v3_11_3::json&, const std::string&)     primary file handle: 3
[1 ms, 1745 us] [tid 359792] void try_parse_mods_file(Settings*, Settings*, nlohmann::json_abi_v3_11_3::json&, const std::string&)     preview_filename: 'preview_image.jpg'
[1 ms, 1746 us] [tid 359792] void try_parse_mods_file(Settings*, Settings*, nlohmann::json_abi_v3_11_3::json&, const std::string&)     preview_filesize: 15181 bytes
[1 ms, 1748 us] [tid 359792] void try_parse_mods_file(Settings*, Settings*, nlohmann::json_abi_v3_11_3::json&, const std::string&)     preview file handle: 4
[1 ms, 1749 us] [tid 359792] void try_parse_mods_file(Settings*, Settings*, nlohmann::json_abi_v3_11_3::json&, const std::string&)     total_files_sizes: 570
[1 ms, 1751 us] [tid 359792] void try_parse_mods_file(Settings*, Settings*, nlohmann::json_abi_v3_11_3::json&, const std::string&)     min_game_branch: ''
[1 ms, 1753 us] [tid 359792] void try_parse_mods_file(Settings*, Settings*, nlohmann::json_abi_v3_11_3::json&, const std::string&)     max_game_branch: ''
[1 ms, 1754 us] [tid 359792] void try_parse_mods_file(Settings*, Settings*, nlohmann::json_abi_v3_11_3::json&, const std::string&)     workshop_item_url: 'https://steamcommunity.com/sharedfiles/filedetails/?id=3366485326'
[1 ms, 1755 us] [tid 359792] void try_parse_mods_file(Settings*, Settings*, nlohmann::json_abi_v3_11_3::json&, const std::string&)     preview_url: 'file:///Half-Life 2/bin/steam_settings/mod_images/3366485326/preview_image.jpg'
[...]
[8948 ms, 8948548 us] [tid 359792] void* SteamInternal_ContextInit(void*) initializing
[8948 ms, 8948551 us] [tid 359792] HSteamUser SteamAPI_GetHSteamUser() 
[8948 ms, 8948552 us] [tid 359792] void* SteamInternal_FindOrCreateUserInterface(HSteamUser, const char*) 1 STEAMUGC_INTERFACE_VERSION020
[8948 ms, 8948554 us] [tid 359792] HSteamPipe SteamAPI_GetHSteamPipe() 
[8948 ms, 8948555 us] [tid 359792] virtual void* Steam_Client::GetISteamGenericInterface(HSteamUser, HSteamPipe, const char*) 'STEAMUGC_INTERFACE_VERSION020' 1 1
[8948 ms, 8948560 us] [tid 359792] virtual ISteamUGC* Steam_Client::GetISteamUGC(HSteamUser, HSteamPipe, const char*) STEAMUGC_INTERFACE_VERSION020
[8948 ms, 8948562 us] [tid 359792] virtual uint32 Steam_UGC::GetNumSubscribedItems() 
[8948 ms, 8948564 us] [tid 359792] virtual uint32 Steam_UGC::GetNumSubscribedItems()   Steam_UGC::GetNumSubscribedItems = 1
[8948 ms, 8948566 us] [tid 359792] virtual uint32 Steam_UGC::GetSubscribedItems(PublishedFileId_t*, uint32) 0x6086bb80 1
[8948 ms, 8948572 us] [tid 359792] virtual UGCQueryHandle_t Steam_UGC::CreateQueryUGCDetailsRequest(PublishedFileId_t*, uint32) 0x6086bb80, max file IDs = [1]
[8948 ms, 8948577 us] [tid 359792] virtual UGCQueryHandle_t Steam_UGC::CreateQueryUGCDetailsRequest(PublishedFileId_t*, uint32)   file ID = 3366485326
[8948 ms, 8948583 us] [tid 359792] UGCQueryHandle_t Steam_UGC::new_ugc_query(bool, std::set<long long unsigned int>) handle = 51
[8948 ms, 8948586 us] [tid 359792] virtual bool Steam_UGC::SetReturnLongDescription(UGCQueryHandle_t, bool) // TODO
[8948 ms, 8948588 us] [tid 359792] virtual bool Steam_UGC::SetReturnKeyValueTags(UGCQueryHandle_t, bool) // TODO
[8948 ms, 8948590 us] [tid 359792] virtual SteamAPICall_t Steam_UGC::SendQueryUGCRequest(UGCQueryHandle_t) 51
[8948 ms, 8948598 us] [tid 359792] SteamAPICall_t SteamCallResults::addCallResult(SteamAPICall_t, int, void*, unsigned int, double, bool) 3401
[8948 ms, 8948606 us] [tid 359792] SteamAPICall_t SteamCallResults::addCallResult(SteamAPICall_t, int, void*, unsigned int, double, bool) 3401
[8948 ms, 8948608 us] [tid 359792] void SteamAPI_RegisterCallResult(CCallbackBase*, SteamAPICall_t) 625884168221758937 0xdf11d4a4
[8948 ms, 8948610 us] [tid 359792] void Steam_Client::RegisterCallResult(CCallbackBase*, SteamAPICall_t) 625884168221758937 3401
[8948 ms, 8948612 us] [tid 359792] void SteamCallResults::addCallBack(SteamAPICall_t, CCallbackBase*) new cb for call result [api id=625884168221758937, result k_iCallback=3401] 0xdf11d4a4
[8948 ms, 8948615 us] [tid 359792] virtual SteamAPICall_t Steam_UGC::StopPlaytimeTrackingForAllItems() 
[8948 ms, 8948617 us] [tid 359792] SteamAPICall_t SteamCallResults::addCallResult(SteamAPICall_t, int, void*, unsigned int, double, bool) 3411
[8948 ms, 8948621 us] [tid 359792] SteamAPICall_t SteamCallResults::addCallResult(SteamAPICall_t, int, void*, unsigned int, double, bool) 3411
[8948 ms, 8948624 us] [tid 359792] virtual SteamAPICall_t Steam_UGC::StartPlaytimeTracking(PublishedFileId_t*, uint32) 
[8948 ms, 8948625 us] [tid 359792] SteamAPICall_t SteamCallResults::addCallResult(SteamAPICall_t, int, void*, unsigned int, double, bool) 3411
[8948 ms, 8948628 us] [tid 359792] SteamAPICall_t SteamCallResults::addCallResult(SteamAPICall_t, int, void*, unsigned int, double, bool) 3411
[...]
[10242 ms, 10242871 us] [tid 359792] void SteamCallResults::runCallResults() Calling callresult 0xdf11d4a4 3401, kind=1 (0=callback, 1=call result)
[10242 ms, 10242874 us] [tid 359792] virtual uint32 Steam_UGC::GetQueryUGCNumTags(UGCQueryHandle_t, uint32) // TODO
[10242 ms, 10242884 us] [tid 359792] virtual bool Steam_UGC::GetQueryUGCResult(UGCQueryHandle_t, uint32, SteamUGCDetails_t*) 51 0 0x6481a170
[10242 ms, 10242886 us] [tid 359792] bool Steam_UGC::internal_GetQueryUGCResult(UGCQueryHandle_t, uint32, SteamUGCDetails_t*, IUgcItfVersion) 51 [0] 0x6481a170 <1>
[10242 ms, 10242888 us] [tid 359792] void Steam_UGC::set_details(PublishedFileId_t, SteamUGCDetails_t*, IUgcItfVersion)   mod is installed, setting details
[10242 ms, 10242890 us] [tid 359792] virtual uint32 Steam_UGC::GetQueryUGCNumKeyValueTags(UGCQueryHandle_t, uint32) // TODO
[10242 ms, 10242900 us] [tid 359792] virtual uint32 Steam_UGC::GetItemState(PublishedFileId_t) 3366485326
[10242 ms, 10242903 us] [tid 359792] virtual uint32 Steam_UGC::GetItemState(PublishedFileId_t)   mod is subscribed and installed
[10242 ms, 10242905 us] [tid 359792] virtual bool Steam_UGC::GetItemInstallInfo(PublishedFileId_t, uint64*, char*, uint32, uint32*) 3366485326 0xfffae7b0 0xfffae7bc [4096] 0xfffae7ac
[10242 ms, 10242908 us] [tid 359792] virtual bool Steam_UGC::GetItemInstallInfo(PublishedFileId_t, uint64*, char*, uint32, uint32*)   final mod path: '/Half-Life 2/bin/steam_settings/mods/3366485326'
[10242 ms, 10242910 us] [tid 359792] virtual SteamAPICall_t Steam_UGC::GetUserItemVote(PublishedFileId_t) 
[10242 ms, 10242912 us] [tid 359792] SteamAPICall_t SteamCallResults::addCallResult(SteamAPICall_t, int, void*, unsigned int, double, bool) 3409
[10242 ms, 10242917 us] [tid 359792] SteamAPICall_t SteamCallResults::addCallResult(SteamAPICall_t, int, void*, unsigned int, double, bool) 3409
[10242 ms, 10242919 us] [tid 359792] void SteamAPI_RegisterCallResult(CCallbackBase*, SteamAPICall_t) 1753078765601400393 0x6481c7e0
[10242 ms, 10242921 us] [tid 359792] void Steam_Client::RegisterCallResult(CCallbackBase*, SteamAPICall_t) 1753078765601400393 3409
[10242 ms, 10242923 us] [tid 359792] void SteamCallResults::addCallBack(SteamAPICall_t, CCallbackBase*) new cb for call result [api id=1753078765601400393, result k_iCallback=3409] 0x6481c7e0
[10242 ms, 10242925 us] [tid 359792] virtual SteamAPICall_t Steam_Remote_Storage::UGCDownload(UGCHandle_t, uint32) 4
[10242 ms, 10242929 us] [tid 359792] virtual SteamAPICall_t Steam_Remote_Storage::UGCDownload(UGCHandle_t, uint32)   QueryUGCRequest data.m_pchFileName = 'preview_image.jpg'
[10242 ms, 10242934 us] [tid 359792] SteamAPICall_t SteamCallResults::addCallResult(SteamAPICall_t, int, void*, unsigned int, double, bool) 1317
[10242 ms, 10242937 us] [tid 359792] SteamAPICall_t SteamCallResults::addCallResult(SteamAPICall_t, int, void*, unsigned int, double, bool) 1317
[10242 ms, 10242940 us] [tid 359792] void SteamAPI_RegisterCallResult(CCallbackBase*, SteamAPICall_t) 7454305387762253380 0x6481c840
[10242 ms, 10242941 us] [tid 359792] void Steam_Client::RegisterCallResult(CCallbackBase*, SteamAPICall_t) 7454305387762253380 1317
[10242 ms, 10242943 us] [tid 359792] void SteamCallResults::addCallBack(SteamAPICall_t, CCallbackBase*) new cb for call result [api id=7454305387762253380, result k_iCallback=1317] 0x6481c840
[10242 ms, 10242981 us] [tid 359792] void SteamCallResults::runCallResults() callresult done
[10242 ms, 10242986 us] [tid 359792] void SteamCallResults::runCallResults() Calling callresult 0xc2019250 1102, kind=0 (0=callback, 1=call result)
[10242 ms, 10242988 us] [tid 359792] void SteamCallResults::runCallResults() callresult done
[10242 ms, 10242990 us] [tid 359792] void SteamCallResults::runCallResults() erase to_delete
[...]
[10244 ms, 10244993 us] [tid 359792] void SteamCallResults::runCallResults() Calling callresult 0x6481c840 1317, kind=1 (0=callback, 1=call result)
[10244 ms, 10244995 us] [tid 359792] virtual bool Steam_Remote_Storage::GetUGCDetails(UGCHandle_t, AppId_t*, char**, int32*, CSteamID*) 4
[10245 ms, 10245001 us] [tid 359792] virtual int32 Steam_Remote_Storage::UGCRead(UGCHandle_t, void*, int32, uint32, EUGCReadAction) 4, 0x649f2e70, 15181, 0, 0
[10245 ms, 10245003 us] [tid 359792] virtual int32 Steam_Remote_Storage::UGCRead(UGCHandle_t, void*, int32, uint32, EUGCReadAction)   source = AfterSendQueryUGCRequest || FromUGCDownloadToLocation [1]
[10245 ms, 10245109 us] [tid 359792] virtual int32 Steam_Remote_Storage::UGCRead(UGCHandle_t, void*, int32, uint32, EUGCReadAction)   mod file '/Half-Life 2/bin/steam_settings/mod_images/3366485326/preview_image.jpg' [15181]
[10245 ms, 10245113 us] [tid 359792] virtual int32 Steam_Remote_Storage::UGCRead(UGCHandle_t, void*, int32, uint32, EUGCReadAction)   read bytes = 15181
[10248 ms, 10248530 us] [tid 359792] void SteamCallResults::runCallResults() callresult done
[10248 ms, 10248540 us] [tid 359792] void SteamCallResults::runCallResults() erase to_delete
[10248 ms, 10248544 us] [tid 359792] void Steam_Client::RunCallbacks(bool, bool) done ******************************************************
@otavepto, I believe you are the latest to have touched mods handling, have you any idea why it is not working?
@otavepto
Copy link
Contributor

I don't know man, I'm tired of this project.
Try setting pnappid. Game also asked for preview file, I don't see anything related to primary one, there should be at least one request for it or something

@Edremon
Copy link
Contributor Author

Edremon commented Nov 25, 2024

I don't know man, I'm tired of this project.

:(

Try setting pnappid.

Oh yes, forgot to set that one, but sadly no change.

Game also asked for preview file, I don't see anything related to primary one, there should be at least one request for it or something

Only lines with "primary" are the try_parse_mods_file one.

@Edremon
Copy link
Contributor Author

Edremon commented Nov 25, 2024

Another weird thing is this, with developer mod, in "My Uploads", I can click "Edit Item" and have this window shown. Notice how the content folder is empty.

image

@Detanup01
Copy link
Owner

Detanup01 commented Nov 25, 2024

Uploading isnt written (As far as I remember)

@Edremon
Copy link
Contributor Author

Edremon commented Nov 25, 2024

It's just that I expect "Content Folder" to be filled with path to mod, just like title, description or preview image get filled. I obviously expect all social features to not be implemented but that not important.

I also tested a Windows build and it's the same behavior, no preview image without implementing GetUGCDetails and broken mods whether that function get implemented or not. The only difference is that for some reason, I can edit mod that aren't my own on Linux, but on Windows, I only see those that have steam_id_owner set to my own.

@otavepto
Copy link
Contributor

Found the problem ding ding ding!
This mod expects the size of both workshop_000.vpk + workshop_dir.vpk as the primary_file_size, here's a working config (you can ignore mod path, I just didn't want to copy/paste anything).

Notice both primary_filesize and total_files_sizes

{
    "3366485326": {
        "steam_id_owner": 76561198139810670,
        "path": "C:\\Program Files (x86)\\Steam\\steamapps\\workshop\\content\\220\\3366485326",
        "title": "GoldSource GUI [HL2]",
        "tags": "assets,materials,ui",
        "total_files_sizes": 43663,
        "primary_filesize": 43663,
        "description": "Old Steam/GoldSource styled GUI",
        "primary_filename": "workshop_dir.vpk",
        "preview_filename": "download.png"
    }
}

Found this by creating a dump of the actual returned data from real steamclient64.dll.
Thanks @Edremon for the implementation btw.

I'd recommend initializing the function params before doing any checks

    PRINT_DEBUG("%llu // TODO", hContent);
    std::lock_guard<std::recursive_mutex> lock(global_mutex);
    
    if (pnAppID) *pnAppID = settings->get_local_game_id().AppID();
    if (pSteamIDOwner) *pSteamIDOwner = k_steamIDNil;
    if (pnFileSizeInBytes) *pnFileSizeInBytes = 0;
    if (ppchName) *ppchName = nullptr;

...
...

Many games don't even care about the return of the function, only their own buffers (since in real scenario Steam won't fail but here we can fail since the emulation might not always be accurate).
Another thing, at the end of the function when setting the buffers always check if they are not null

...
...
        if (ppchName) {
            *ppchName = new char[mod_name.size() + 1];
            std::strcpy(*ppchName, mod_name.c_str());
        }
        if (pnFileSizeInBytes) *pnFileSizeInBytes = mod_size;
        if (pSteamIDOwner) *pSteamIDOwner = mod.steamIDOwner;

Fun trivia:

  • Steam sets primary file handle for this mod to k_UGCHandleInvalid ! (this explains why the mod never bothered to look at its own primary file ID since it is composite)
  • SteamUGCDetails_t::m_pchFileName is set to null/empty string
  • SteamUGCDetails_t::m_rgchTitle is set to null/empty string
  • The file handles returned by steam seem to count in reverse (the preview file handle was closer to uint64 max)

@Edremon
Copy link
Contributor Author

Edremon commented Nov 26, 2024

Found the problem ding ding ding!

Nice!

This mod expects the size of both workshop_000.vpk + workshop_dir.vpk as the primary_file_size

So it got that info from GetItemInstallInfo then wasn't happy because it was the wrong size? Are we sure that this function just always return the total filesize? Steam doc says "Returns the size of the workshop item in bytes.".

I'd recommend initializing the function params before doing any checks

    PRINT_DEBUG("%llu // TODO", hContent);
    std::lock_guard<std::recursive_mutex> lock(global_mutex);
    
    if (pnAppID) *pnAppID = settings->get_local_game_id().AppID();
    if (pSteamIDOwner) *pSteamIDOwner = k_steamIDNil;
    if (pnFileSizeInBytes) *pnFileSizeInBytes = 0;
    if (ppchName) *ppchName = nullptr;

...
...

Many games don't even care about the return of the function, only their own buffers (since in real scenario Steam won't fail but here we can fail since the emulation might not always be accurate). Another thing, at the end of the function when setting the buffers always check if they are not null

...
...
        if (ppchName) {
            *ppchName = new char[mod_name.size() + 1];
            std::strcpy(*ppchName, mod_name.c_str());
        }
        if (pnFileSizeInBytes) *pnFileSizeInBytes = mod_size;
        if (pSteamIDOwner) *pSteamIDOwner = mod.steamIDOwner;

Will apply these fixes and open a PR if everything is working correctly. Why the TODO in print? I noticed a lot of TODO in functions that seemed to be fully implemented.

Fun trivia:

* Steam sets primary file handle for this mod to `k_UGCHandleInvalid` ! (this explains why the mod never bothered to look at its own primary file ID since it is composite)

* `SteamUGCDetails_t::m_pchFileName` is set to null/empty string

* `SteamUGCDetails_t::m_rgchTitle` is set to null/empty string

* The file handles returned by steam seem to count in reverse (the preview file handle was closer to uint64 max)

I don't really understand all that trivia, but I guess it's a bit special because it's basically compressed; workshop_dir.vpk and workshop_xxx.vpk is the same archive split in multiple files.

@otavepto
Copy link
Contributor

Not sure about that function it's confusing since other games work without doing this manual modification (Human Fall Flat for example). Needs testing by dumping the data from many other games :/
We used to add the debug prints in places where nothing is implemented, or when not sure that would cover the majority of the cases, if even implemented correctly. There is no rule really.

@Edremon
Copy link
Contributor Author

Edremon commented Nov 26, 2024

I sadly still can't get it to work:

{
    "3366485326": {
        "steam_id_owner": 76561198139810670,
        "title": "GoldSource GUI [HL2]",
        "tags": "assets,materials,ui",
        "total_files_sizes": 43663,
        "primary_filesize": 43663,
        "description": "Old Steam/GoldSource styled GUI",
        "primary_filename": "workshop_dir.vpk",
        "preview_filename": "preview_image.jpg"
    }
}

I verified, my files are exactly 43663 bytes. I even tried putting mods in steam path and specifying it. Also tried on Windows build (without trying steam path in that case).

Using latest master (30f1f56) with this patch:

diff --git a/dll/steam_remote_storage.cpp b/dll/steam_remote_storage.cpp
index 7ab28fbf..d8541f15 100644
--- a/dll/steam_remote_storage.cpp
+++ b/dll/steam_remote_storage.cpp
@@ -495,9 +495,34 @@ bool Steam_Remote_Storage::GetUGCDownloadProgress( UGCHandle_t hContent, uint32
 // Gets metadata for a file after it has been downloaded. This is the same metadata given in the RemoteStorageDownloadUGCResult_t call result
 bool Steam_Remote_Storage::GetUGCDetails( UGCHandle_t hContent, AppId_t *pnAppID, STEAM_OUT_STRING() char **ppchName, int32 *pnFileSizeInBytes, STEAM_OUT_STRUCT() CSteamID *pSteamIDOwner )
 {
-    PRINT_DEBUG_ENTRY();
+    PRINT_DEBUG("%llu", hContent);
     std::lock_guard<std::recursive_mutex> lock(global_mutex);
-    
+    if (hContent == k_UGCHandleInvalid) return k_uAPICallInvalid;
+
+    if (pnAppID) *pnAppID = settings->get_local_game_id().AppID();
+    if (pSteamIDOwner) *pSteamIDOwner = k_steamIDNil;
+    if (pnFileSizeInBytes) *pnFileSizeInBytes = 0;
+    if (ppchName) *ppchName = nullptr;
+
+    if (auto query_res = ugc_bridge->get_ugc_query_result(hContent)) {
+        auto mod = settings->getMod(query_res.value().mod_id);
+        auto &mod_name = query_res.value().is_primary_file
+            ? mod.primaryFileName
+            : mod.previewFileName;
+        int32 mod_size = query_res.value().is_primary_file
+            ? mod.primaryFileSize
+            : mod.previewFileSize;
+
+        if (ppchName) {
+            *ppchName = new char[mod_name.size() + 1];
+            std::strcpy(*ppchName, mod_name.c_str());
+        }
+        if (pnFileSizeInBytes) *pnFileSizeInBytes = mod_size;
+        if (pSteamIDOwner) *pSteamIDOwner = mod.steamIDOwner;
+
+        return true;
+    }
+
     return false;
 }
 

@universal963
Copy link
Contributor

  •    if (ppchName) {
    
  •        *ppchName = new char[mod_name.size() + 1];
    
  •        std::strcpy(*ppchName, mod_name.c_str());
    

BTW, don't use new here because no corresponding delete can be run, causing memory leak. You can use if (ppchName) { *ppchName = mod_name.c_str(); } instead.

@Edremon
Copy link
Contributor Author

Edremon commented Nov 26, 2024

BTW, don't use new here because no corresponding delete can be run, causing memory leak. You can use if (ppchName) { *ppchName = mod_name.c_str(); } instead.

That wouldn't even compile. It's the job of the caller to manage that string and free its memory.

@universal963
Copy link
Contributor

universal963 commented Nov 26, 2024

Apologies for forgetting type conversion there, it should be if (ppchName) { *ppchName = (char*)mod_name.c_str(); }.
However I currently don't agree on that point because no public docs or example code suggest caller to free memory. And no docs state which function to properly free it, resulting a question: should the app use delete[] or free() to do it without knowing implementation details ahead?

@otavepto
Copy link
Contributor

@Edremon I forgot to push a damn fix for querying tags count in Steam_UGC::GetQueryUGCNumTags(), I thought I opened the PR already yesterday!
I'll open it now, sorry for the confusion (I genuinely confused even myself, re-cloned repo and proceeded to debug this thing for ~3 hours only to discover I wiped the changes and forgot to open the damn PR!)
image

@universal963 You are right, we can create a simple hash map or something as a strong reference storage, but laziness is king 😄

@Edremon
Copy link
Contributor Author

Edremon commented Nov 26, 2024

image

The filesize is not even needed, the only needed one seems to be primary_filename (and tags currently but might be fixed). Filesize and tags that are shown as "type" in workshop are the only settings needed for HL2.

    "3366485326": {
        "title": "GoldSource GUI [HL2]",
        "tags": "assets,materials,ui",
        "primary_filesize": 43663,
        "preview_filename": "preview_image.jpg"
    }

As for best way to implement GetUGCDetails, I'm a bit rusty in C++, but I really don't think using c_str() is the best solution. That string might be destroyed and have those pointers be dangling.

@universal963
Copy link
Contributor

Indeed that string might be destroyed at some point but it should be noted that:

  1. c_str() returns the string which is bound to the object, so if the object doesn't get changed, it will probably not get invalidated. Since UGC can't be changed dynamically, I think it is the case.
  2. If you have tested with real Steam for a bit time, you will find that many apis, unless specified in docs, return the pointers which is temporary and will be invalidated at any time, so if an app tries to keep them, it might cause problems already on real Steam.

@Edremon
Copy link
Contributor Author

Edremon commented Nov 27, 2024

Seems like HL2 want "type" tag or it put stuff as broken. Made a small script to generate a mods.json from mod directory using ISteamRemoteStorage/GetPublishedFileDetails web API (doesn't require API key), allow populating title, description, steam_id_owner, time_created, time_updated, tags, primary_filesize and preview_filename (that get downloaded). Annoying thing is that API always have filename set to an empty string and if I don't set primary_filename or use a non existing file, it show as broken item in game. I'm not sure if it's an emulator quirk or if game is really expecting it and API is missing that return. Edit: Was an emulator bug.

image

@Edremon
Copy link
Contributor Author

Edremon commented Dec 2, 2024

Fixed with #103, #104 and #106.

Here a small script to create mods settings from a steam_settings directory that have mods in either 123456 or 123456 Name format (that how they are named where I download them). https://gist.github.com/Edremon/a1f326f84314b3eeacb8cef0c64f6ae1

@Edremon Edremon closed this as completed Dec 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants