主頁 > 移動端開發 > C#WINAPI-創建一個新的DACL

C#WINAPI-創建一個新的DACL

2022-06-20 21:41:17 移動端開發

我正在嘗試在我使用 LogonUserExW 登錄的用戶的背景關系中啟動一個行程。

為此,我需要修改 Winstation "Winsta0" 和桌面 "Default" 的 DACL。我所做的是使用 GetSecurityInfo 獲取 DACL 和安全描述符(我實際上不需要也不需要使用)。然后我呼叫 GetAclInformation 兩次以獲取有關 ACL 大小和修訂的資訊。

ACL_SIZE_INFORMATION aclInfoSizeOut = new ACL_SIZE_INFORMATION();
GetAclInformation(daclWinsta, out aclInfoSizeOut, Marshal.SizeOf(aclInfoSizeOut), ACL_INFORMATION_CLASS.AclSizeInformation);
ACL_REVISION_INFORMATION aclInfoRevisionOut = new ACL_REVISION_INFORMATION();
GetAclInformation(daclWinsta, out aclInfoRevisionOut, Marshal.SizeOf(aclInfoRevisionOut), ACL_INFORMATION_CLASS.AclRevisionInformation);

獲得資料后,我會計算新 ACL 的所需大小:

int cbNewACL = Convert.ToInt32(aclInfoSizeOut.AclBytesInUse   Marshal.SizeOf(allowedAce)   GetLengthSid(userpSid) - sizeof(UInt32));

然后根據https://web.archive.org/web/20121231022835/http://support.microsoft.com/kb/102102我為新的 ACL 分配記憶體使用

IntPtr pNewAcl = LocalAlloc(LocalAllocFlags.LPTR, cbNewACL);

這就是我開始感到困惑的地方。我不明白這實際上是如何作業的。使用 LocalAlloc 我現在有一個指向我指定大小的記憶體的指標。然后我初始化應該在該記憶體塊中的新 ACL,但是如何?因為一旦我使用下面的函式,指標實際上就被重寫了。有趣的是,每次我呼叫該函式時它總是具有相同的值。而 Locallloc 回傳的指標總是不同的。

InitializeAcl(out pNewAcl, cbNewACL, aclInfoRevisionOut.AclRevision);

InitializeAcl 函式中的 pAcl 定義為“[out] pAcl 指向要由此函式初始化的 ACL 結構的指標。在呼叫此函式之前為 pAcl 分配記憶體。” 我會理解它是否作為 REF 傳遞給函式以知道在哪里實際初始化 ACL,但 REF 根本不會改變它。

另一件事是當我呼叫 InitializeAcl 時,cbNewAcl 被設定為 0。這完全超出了我的想象。它如何以及為什么會改變價值?

然后,當我呼叫 AddAccessAllowedAce 時,它??會在我從現有 ACL 獲取資料之前完全弄亂我設定的結構 - aclInfoSizeOut 和 aclInfoRevisionOut 設定為無意義的值。比如count 0,bytes in use 78548557(有些高的數字)。并且 cbNewAcl 被更改為 1。我不知道為什么會這樣。

AddAccessAllowedAce(ref pNewAcl, aclInfoRevisionOut.AclRevision, ACCESS_MASK.READ_CONTROL | ACCESS_MASK.WINSTA_ALL_ACCESS, userpSid);

下面是我“作業”的完整代碼——我獲取用戶的令牌和 psid,我查詢現有的 DACL,我查詢 DACL 的成員并獲取他們的字串 SID,但僅此而已。我無法創建新的 DACL。也將其與我的評論一起發布。我希望我沒有忘記任何簽名,因為 VS 中的代碼比我實際發布的要長一些。

[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUserExW(
            string lpszUsername,
            string lpszDomain,
            string lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            out IntPtr phToken,
            out IntPtr ppLogonSid,
            out IntPtr ppProfileBuffer,
            out IntPtr pdwProfileLength,
            out QUOTA_LIMITS pQuotaLimits
            );
[DllImport("Advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int ConvertSidToStringSidW(
                IntPtr SidPtr,
                out IntPtr SidString
            );          
[DllImport("Advapi32.dll", SetLastError = true)]
public static extern int GetSecurityInfo
            (
                IntPtr handle,
                SE_OBJECT_TYPE ObjectType,
                SECURITY_INFORMATION SecurityInfo,
                out IntPtr ppsidOwner,
                out IntPtr ppsidGroup,
                out IntPtr ppDacl,
                out IntPtr ppSacl,
                out IntPtr ppSecurityDescriptor
            );          
[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr GetProcessWindowStation();          
[DllImport("Advapi32.dll", SetLastError = true)]
public static extern bool GetAclInformation(
                IntPtr pAcl,
                out ACL_SIZE_INFORMATION pAclInformation,
                int nAclInformationLength,
                ACL_INFORMATION_CLASS aclInformationClass
            );
public struct ACL_SIZE_INFORMATION
{
    public uint AceCount;
    public uint AclBytesInUse;
    public uint AclBytesFree;
}
public struct ACL_REVISION_INFORMATION
{
    public int AclRevision;
}
public enum  ACL_INFORMATION_CLASS
{
            AclRevisionInformation = 1,
            AclSizeInformation
}

public struct QUOTA_LIMITS
{
            public int PagedPoolLimit;
            public int NonPagedPoolLimit;
            public int MinimumWorkingSetSize;
            public int MaximumWorkingSetSize;
            public int PagefileLimit;
            public Int64 TimeLimit;
}
ublic struct ACCESS_ALLOWED_ACE
{
    public ACE_HEADER Header;
    public ACCESS_MASK Mask;
    public UInt16 SidStart;
}
ublic enum LocalAllocFlags : uint
{
    LHND = 0x0042,
    LMEM_FIXED = 0x0000,
    LMEM_MOVEABLE = 0x0002,
    LMEM_ZEROINIT = 0x0040,
    LPTR = 0x0040
}

[DllImport("Kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalAlloc(LocalAllocFlags flags, int bytes);
DllImport("Advapi32.dll", SetLastError = true)]
public static extern bool InitializeAcl(
    out IntPtr pAcl,
    int nAclLength,
    int dwAclRevision
    );
public static extern int AddAccessAllowedAce
    (
        ref IntPtr pAcl,
        /*  ACL_REVISION    2
            ACL_REVISION_DS 4
         */
        int dwAceRevision,
        ACCESS_MASK AccessMask,
        IntPtr pSid
    );
    
    
    
LogonUserExW("XXXXX", "FFFFF", "BBBBB", 2, 0, out IntPtr userToken, out IntPtr userpSid, out IntPtr profileBuffer, out IntPtr profileLength, out QUOTA_LIMITS profQuotaLimits);

ConvertSidToStringSidW(userpSid, out IntPtr ptrSid);

string stringSid = Marshal.PtrToStringUni(ptrSid);

IntPtr winstaHandle = GetProcessWindowStation();

//get security descriptor for the station
int getDescriptorResult = GetSecurityInfo(winstaHandle, SE_OBJECT_TYPE.SE_WINDOW_OBJECT, SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | SECURITY_INFORMATION.PROTECTED_DACL_SECURITY_INFORMATION, out IntPtr owner, out IntPtr group, out IntPtr daclWinsta, out IntPtr Sacl, out IntPtr secDescriptor);

//this actually works but shows different values than ACL_SIZE_INFORMATION and ACL_REVISION_INFORMATION
//maybe DACL is in different structure than ACL struct? could not find any other struct in the documentation
ACL aclStruct = (ACL)Marshal.PtrToStructure(daclWinsta, typeof(ACL));

ACL_SIZE_INFORMATION aclInfoSizeOut = new ACL_SIZE_INFORMATION();
GetAclInformation(daclWinsta, out aclInfoSizeOut, Marshal.SizeOf(aclInfoSizeOut), ACL_INFORMATION_CLASS.AclSizeInformation);
ACL_REVISION_INFORMATION aclInfoRevisionOut = new ACL_REVISION_INFORMATION();
GetAclInformation(daclWinsta, out aclInfoRevisionOut, Marshal.SizeOf(aclInfoRevisionOut), ACL_INFORMATION_CLASS.AclRevisionInformation);
//count:17 bytes:436

//create a new ACL just to get its size for the cbNewACL value
ACCESS_ALLOWED_ACE allowedAce = new ACCESS_ALLOWED_ACE();

int cbNewACL = Convert.ToInt32(aclInfoSizeOut.AclBytesInUse   Marshal.SizeOf(allowedAce)   GetLengthSid(userpSid) - sizeof(UInt32));
//??? for some reason cbNewACL gets cleared after calling InitializeAcl
IntPtr pNewAcl = LocalAlloc(LocalAllocFlags.LPTR, cbNewACL);


InitializeAcl(out pNewAcl, cbNewACL, aclInfoRevisionOut.AclRevision);
//just for test try to add it right away
//AddAccessAllowedAce(ref pNewAcl, aclInfoRevisionOut.AclRevision, ACCESS_MASK.READ_CONTROL | ACCESS_MASK.WINSTA_ALL_ACCESS, userpSid);
//after calling AddAccessAllowedAce the information is structs aclInfoSizeOut and aclInfoRevisionOut gets destroyed
//cbNewAcl gets set to 1
//???




bool newAceAdded = false;
bool userSidAlreadyExists = false;
//copy old ACEs into the new ACL
if (aclInfoSizeOut.AceCount != 0)
{
    int indexInNewAcl = 0;
    for (int i=0;i<aclInfoSizeOut.AceCount;i  )
    {
        //get ace and add it to the new ACL
        GetAce(daclWinsta, i, out IntPtr existingAce);
        if (existingAce != IntPtr.Zero)
        {
            //get ace header from the ACE pointer to identify the ACE type
            //get first 4 bytes from the pointer starting at 0 since ACE_HEADER struct is of size 4
            //once we have it in the byte array, create a GC handle with the bytes - this allocates them in a managed memory
            //then read it to the structure using Marshal.PtrToStructure

            ACE_HEADER aceHeader = new ACE_HEADER();
            byte[] aceHeaderBytes = new byte[Marshal.SizeOf(aceHeader)];
            Marshal.Copy(existingAce, aceHeaderBytes, 0, aceHeaderBytes.Length);
            GCHandle aceHeaderBytesHandle = GCHandle.Alloc(aceHeaderBytes, GCHandleType.Pinned);
            try
            {
                aceHeader = (ACE_HEADER)Marshal.PtrToStructure(aceHeaderBytesHandle.AddrOfPinnedObject(), typeof(ACE_HEADER));
                switch (aceHeader.AceType)
                {
                    case 0:
                    case 1:
                        //0 allowed - ACCESS_ALLOWED_ACE, 1 denied - ACCESS_DENIED_ACE 
                        //the two have the same members definied in their structure.
                        //the declaration below is not explicitly needed in our case, but might be useful for some other things
                        //ACCESS_ALLOWED_ACE existingDeniedAllowedACE = (ACCESS_ALLOWED_ACE)Marshal.PtrToStructure(existingAce, typeof(ACCESS_ALLOWED_ACE));
                        //0 and 1 in this context are together in one clause since they are definied by the same members in the same order, thus allowing us
                        //to get SidStart from the same offset from the ACE IntPtr
                        //GUESSING: SidStart starts at 8th byte in the struct, therefore an offset of 8 bytes - size of ACE_HEADER and ACCESS_MASK (Uint16)
                        //if for example the ACE type would be definied by more members with SidStart at the end, we would have to calculate the offset by
                        //the sum of all members except for SidStart
                        IntPtr sidPtrInAce = IntPtr.Zero;
                        
                        try
                        {
                            sidPtrInAce = IntPtr.Add(existingAce, 8);
                            ConvertSidToStringSidW(sidPtrInAce, out IntPtr pSidStringInAce);
                            if (pSidStringInAce == IntPtr.Zero) { throw new Exception("failed to get pSidString"); }
                            string sidStringInAce = Marshal.PtrToStringUni(pSidStringInAce);
                            Console.WriteLine(sidStringInAce);
                            if (sidStringInAce == stringSid) { userSidAlreadyExists = true; }
                        } catch {  }

                        break;
                }
                if (userSidAlreadyExists) { break; }

                //if the ACE was not added AND the processing ACE is not NON-INHERITED DENIED and is not DENIED for Object AND is not Enable for Object
                //then add our ADE to ensure it is in the correct order
                //Windows 2000 and later ACE ordering in ACL:
                //Non-Inherited -> Inherited
                //withing each of the two groups the ACEs are also in the following order
                //Disable for the object
                //Disable for the subject of the object
                //Enable for the object
                //Enable for the subject of the object
                //So basically if we hit anything but NON-Inherited disable for the object, NON-Inherited Disable for the subject of the object and NON-Inherited Enable for the object
                //then we should add our ACE - this will ensure correct order
                //^ could not figure out how to distinguish between Enable for the object and Enable for the subject of the object

                //instead, add the ACE the moment we find an inherited ACE

                if (!newAceAdded && isAceInherited(aceHeader.AceFlags))
                {
                    //AddAccessAllowedAce(ref pNewAcl, aclInfoRevisionOut.AclRevision, ACCESS_MASK.READ_CONTROL | ACCESS_MASK.WINSTA_ALL_ACCESS, userpSid);
                    newAceAdded = true;
                    //indexInNewAcl  ;
                }

                //AddAce(ref pNewAcl, aclInfoRevisionOut.AclRevision, indexInNewAcl, existingAce, aceHeader.AceSize);
                indexInNewAcl  ;
            }
            catch (Exception ex)
            {
                aceHeaderBytesHandle.Free();
            }
        } else
        {
            Console.WriteLine("failed obtaining existing ACE in ACL. quitting"); Console.Read(); return;
        }
    }
}
if (userSidAlreadyExists) { Console.WriteLine("user already exists in the ACL. quit"); Console.Read(); return; }
if (!newAceAdded && !userSidAlreadyExists)
{
    //we did not hit an inherited ACE - probably none exists in the ACL OR AceCount was 0 - no going through ACEs
    //therefore user was not added
    //AddAccessAllowedAce(ref pNewAcl, aclInfoRevisionOut.AclRevision, ACCESS_MASK.READ_CONTROL | ACCESS_MASK.WINSTA_ALL_ACCESS, userpSid);
    newAceAdded = true;
}
if (!newAceAdded)
{
    Console.WriteLine("failed to add new ACE. quit"); Console.Read(); return;
}

CloseHandle(userToken);
LocalFree(userpSid);
Console.WriteLine("ready to quit");
Console.Read();

uj5u.com熱心網友回復:

根據塞爾文的評論:

問題出在 InitializeAcl 簽名上。它不是out,而是in,并且函式在IntPtr指向的地址中初始化了acl。除此之外,我還必須更改 AddAce 和 AddAccessAllowedAce 簽名——將 IntPtr pAcl 參考到 IntPtr 中。

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/493355.html

標籤:C# 温纳皮 acl 解码器

上一篇:如WMI所見,WOW64程式如何覆寫其命令列引數?

下一篇:C#從SidStartACCESS_ALLOWED_ACE獲取pSID,字串SID

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more