天天看點

用一次實戰記錄電池熱更新檔的編寫方法

作者:黑蘋果社群

需要準備的軟體

MaciASL

Hackintool

Hex Fiend(App Store) 或 Hex Fiend(官網)

0x00 建立DSL檔案

用一次實戰記錄電池熱更新檔的編寫方法

儲存并且命名為SSDT-BATT.dsl

0x01 反編譯原來的 DSDT

參考https://jcstaff.club/2019/DSDT-SSDT-battery/的提取和反編譯兩個小節來進行提取,不要用MaciASL來提取,因為MaciASL提取到的DSDT會受到Clover/OC的二進制重命名影響,用Clover提取到的是最好的原廠DSDT。

将 DSDT.aml 和 DSDT.dsl 都複制到桌面,在我們要确定如何做二進制更名時需要用到原本的aml檔案。

0x02 搜尋EC

在DSDT.dsl中搜尋「PNP0C09」來找到你的EC,然後再從EC之中尋找所有的Field

... 表示省略了部分代碼,下同

OperationRegion (ERAM, EmbeddedControl, Zero, 0xFF)
Field (ERAM, ByteAcc, Lock, Preserve)
{
        SMPR,   8, 
        SMST,   8, 
        SMAD,   8, 
        SMCM,   8, 
        SMD0,   256,  //            

全選代碼

複制

像這樣,把逗号後面數字大于8的變量和他們的Offset記錄下來。

Offset怎麼計算呢?

我們先看OperationRegion (ERAM, EmbeddedControl, Zero, 0xFF)這一句,括号裡面第一個參數是區域名ERAM。

接着我們發現所有的Field裡面的第一個參數也都是ERAM,可以确定它們是對同一個記憶體區域進行操作的,這一塊記憶體區域名為 ERAM。

你的DSDT不一定都是 ERAM,如果出現了其他名字的 Field,請尋找對應的 OperationRegion 來确定它的類型和偏移量

OperationRegion (ERAM, EmbeddedControl, Zero, 0xFF)的第二個參數是EmbeddedControl,這個是操作空間類型,我的是 EmbeddedControl,而有的主機闆廠商會選擇将EC資料映射到記憶體中,此時類型為 SystemMemory,且第三個參數通常不為零。

第三個參數是Zero,在ASL中 Zero 和 0 等效。此處參數為操作空間的偏移量,也就是Offset,如果不為零的話我們也要把這個參數記錄下來。

第四個參數是0xFF,為操作空間的最大大小,此處為 0xFF Byte,化成十進制就是255個位元組,不重要。

ok,現在我們知道我們隻有一個操作區 ERAM,偏移量為0。

我們從頭開始看

{
        SMPR,   8, 
        SMST,   8, 
        SMAD,   8, 
        SMCM,   8, 
        SMD0,   256,  //            

全選代碼

複制

前面的 SMPR 是變量名,其類型為FieldUnitObj,後面的 8 是長度,機關為 bit。

8 bit,也就是 1 byte。

是以我們可以确定到第一個箭頭指向的變量SMD0的偏移量是 (8+8+8+8)/8=4 byte

在SSDT-BATT.dsl的注釋中記下來,// 256bit: SMD0 (0x04)

接着到第二段

Offset (0x82), 
        MBST,   8, 
        MCUR,   16,   //            

全選代碼

複制

此處難點為對 Offset(0x82) 的了解,不是空開0x82個位元組的意思,而是将MBST對齊到0x82這個整數位上,是以我們可以直接得到MBST的偏移量為0x82,故MCUR的偏移量為0x83,類推得到 MBRM: 0x85,MBCV: 0x87

同樣地,在注釋中記下來// 16bit: MCUR(0x83), MBRM(0x85), MBCV(0x87)

第三段

Field (ERAM, ByteAcc, NoLock, Preserve)
{
        Offset (0x04), 
        SMW0,   16  //            

全選代碼

複制

同理可得 // 16bit: SMW0(0x04)

以同樣的方法将所有的 Field 中所有的大于 8 bit 的變量都記錄下來,我們的第一節就完成了!

PS:之是以要将大于 8 bit 的變量記錄下來是因為macOS不支援讀取大于 8 bit 的變量,是以我們将在接下來的幾節中将它們拆開為 8 bit 的大小。

0x03 介紹一下要用到的方法

所有的dsl都需要從一個 DefinitionBlock 中開始,我們也不例外。将這一段代碼複制到你的dsl中

DefinitionBlock ("", "SSDT", 2, "ERIC", "BATT", 0) {

}
           

全選代碼

複制

其中 ERIC 可以改成你的英文名,如果超過6個字元就縮寫一下。畢竟是你自己編寫的熱更新檔,著作權還是得有的嘛 XD

接着我們将這一段粘貼到大括号的内部(删除所有中文注釋!!)

Method (B1B2, 2, NotSerialized)
{
    Return ((Arg0 | (Arg1 > 0x08)
}

Method (RE1B, 1, NotSerialized)
{
    OperationRegion (ERM2, EmbeddedControl, Arg0, One) // 作用域為 EmbeddedControl,Arg0 定義起始偏移量
    Field (ERM2, ByteAcc, NoLock, Preserve)
    {
        BYTE,   8 // 指定一個 8 位寄存器映射對應區域資料
    }

    Return (BYTE) // 傳回結果
}
Method (RECB, 2, Serialized)
{
    Arg1 = ((Arg1 + 0x07) >> 0x03) // 計算 Arg1 除 8 并向上取整,位移運算更快
    Name (TEMP, Buffer (Arg1){}) // 初始化作為傳回值的 Buffer
    Arg1 += Arg0 // 加上偏移量,即循環終止值
    Local0 = Zero // 定義 Buffer 索引為 0
    While ((Arg0 > 0x03) // 計算 Arg1 除 8 并向上取整,位移運算更快
    Name (TEMP, Buffer (Arg1){}) // 初始化作為寫入值的 Buffer
    TEMP = Arg2 // 将被寫入的資料或對象指派給 TEMP
    Arg1 += Arg0 // 加上偏移量,即循環終止值
    Local0 = Zero // 定義 Buffer 索引為 0
    While ((Arg0            

全選代碼

複制

這一段是我們将會調用到的方法

Method (B1B2, 2, NotSerialized)是将兩個Byte拼成一個Word的方法,如果你有程式設計基礎你可以從源代碼中看出來這一點。用于讀取,傳入兩個Byte的參數,傳回一個Word。

Method (B1B4, 4, NotSerialized)是将四個Byte拼成一個DWord的方法。調用時傳入四個Byte的參數,傳回一個DWord。

Method (W16B, 3, NotSerialized)是将一個Word拆開成兩個Byte的方法。在使用時需要先初始化兩個Local變量,然後 W16B (Local0, Local1, Arg0)。它會将Arg0拆分後放入Local0和Local1中。無傳回值。

Local0 = 0
Local1 = 0
W16B(Local0, Local1, 0x3344)
           

全選代碼

複制

執行後Local0 = 0x44,Local1 = 0x33

Method (RECB, 2, Serialized)是從記憶體區域中逐位元組讀取的方法。調用時傳入兩個參數,第一個參數為偏移量Offset,第二個參數為長度(bit),傳回一個Byte數組。

Method (WECB, 3, Serialized)是将資料逐位元組寫回記憶體的方法。調用時傳入三個參數,第一個參數是偏移量Offset,第二個參數是長度(bit),第三個參數是要寫入的資料變量。無傳回值。

0x04 拆分變量

還記得我們剛剛找出來的變量嗎?

// 16bit:  BADC(0x70), BFCC(0x72), MCUR(0x83), MBRM(0x85), MBCV(0x87), SMW0(0x04)
// 64bit:  FLD0(0x04)
// 128bit: FLD1(0x04)
// 192bit: FLD2(0x04)
// 256bit: SMD0(0x04), FLD3(0x04)
           

全選代碼

複制

将16bit的拆開為兩個8bit的

比如 BADC -> ADC0, ADC1

按照這樣的模式修改原文

先尋找原文,确定其作用域

用一次實戰記錄電池熱更新檔的編寫方法

如圖可知,其作用域為Scope (_SB.PCI0.LPCB.EC0),故形成了這樣的結構

Scope (_SB.PCI0.LPCB.EC0) {
    OperationRegion(ERM0, EmbeddedControl, 0, 0xFF)
    Field(ERM0, ByteAcc, Lock, Preserve) {
        Offset(0x72),
        BFC0, 8,    // BFCC
        BFC1, 8,
        Offset(0x83),
        MCU0, 8,    // MCUR
        MCU1, 8,
        MBR0, 8,    // MBRM
        MBR1, 8,
        MBC0, 8,    // MBCV
        MBC1, 8
    }
    Field(ERM0, ByteAcc, NoLock, Preserve) {
        Offset (0x04)
        MW00, 8,    // SMW0
        MW01, 8
    }
}
           

全選代碼

複制

此處由于BADC未被使用,是以不需要拆分。其他的将原文複制過來并且拆分,尤其要注意Field是Lock還是NoLock,如果搞錯這個可能會産生未知的後果。

0x05 尋找需要更改的方法

// 16bit:  BADC(0x70), BFCC(0x72), MCUR(0x83), MBRM(0x85), MBCV(0x87), SMW0(0x04)
// 64bit:  FLD0(0x04)
// 128bit: FLD1(0x04)
// 192bit: FLD2(0x04)
// 256bit: SMD0(0x04), FLD3(0x04)
           

全選代碼

複制

在MaciASL中按下⌘F組合鍵對每個變量名進行搜尋,若隻有聲明沒有調用則可以忽略,否則将原方法複制粘貼到SSDT-BATT.dsl中。

這裡給出一個簡單的例子

Method (UPBI, 0, NotSerialized)
{
    Store (^^PCI0.LPCB.EC0.BFCC, Local5)
    If (LAnd (Local5, LNot (And (Local5, 0x8000))))
    {
        ShiftRight (Local5, 0x05, Local5)
        ShiftLeft (Local5, 0x05, Local5)
        Store (Local5, Index (PBIF, One))
        Store (Local5, Index (PBIF, 0x02))
        Divide (Local5, 0x64, , Local2)
        Add (Local2, One, Local2)
        Multiply (Local2, 0x0C, Local4)
        Add (Local4, 0x02, Index (PBIF, 0x05))
        Multiply (Local2, 0x07, Local4)
        Add (Local4, 0x02, Index (PBIF, 0x06))
        Multiply (Local2, 0x0A, Local4)
        Add (Local4, 0x02, FABL)
    }
...
}
           

全選代碼

複制

根據路徑樹确定此方法的Scope為_SB.BAT0

用一次實戰記錄電池熱更新檔的編寫方法

将Scope添加上

Scope(_SB.BAT0)
{  
    Method (UPBI, 0, NotSerialized)
    {
        Store (^^PCI0.LPCB.EC0.BFCC, Local5)
        If (LAnd (Local5, LNot (And (Local5, 0x8000))))
        {
            ShiftRight (Local5, 0x05, Local5)
            ShiftLeft (Local5, 0x05, Local5)
            Store (Local5, Index (PBIF, One))
            Store (Local5, Index (PBIF, 0x02))
            Divide (Local5, 0x64, , Local2)
            Add (Local2, One, Local2)
            Multiply (Local2, 0x0C, Local4)
            Add (Local4, 0x02, Index (PBIF, 0x05))
            Multiply (Local2, 0x07, Local4)
            Add (Local4, 0x02, Index (PBIF, 0x06))
            Multiply (Local2, 0x0A, Local4)
            Add (Local4, 0x02, FABL)
        }
}
           

全選代碼

複制

我們需要修改的是這一句

Store (^^PCI0.LPCB.EC0.BFCC, Local5)
           

全選代碼

複制

這是一句指派語句,作用是将BFCC指派給Local5

我們可以改成

Local5 = B1B2(^^PCI0.LPCB.EC0.BFC0, ^^PCI0.LPCB.EC0.BFC1)
           

全選代碼

複制

其中^^是指代路徑樹之中上上層的節點,^則是指帶的上層節點也就是父節點。

而若是超過16bit的變量則需要用WECB()和RECB()兩個方法來讀取或寫入。

同樣地,我給出一個例子

...
                        If (LLess (Local3, 0x09))
                        {
                            Store (FLD0, Local2)
                        }
...
           

全選代碼

複制

我們可以修改為

...
                        If (LLess (Local3, 0x09))
                        {
                            //Store (FLD0, Local2)
                            Local2 = RECB(0x04, 64)    // 64bit:  FLD0(0x04)
                        }
...
           

全選代碼

複制

注意要放在對應的Scope裡面!并且要将原方法完整地複制下來再做修改!

0x06 添加作業系統判斷

進行作業系統判斷的語句如下

If (_OSI("Darwin"))     // If OS match macOS
{

}
Else
{
    // 調用原方法
}
           

全選代碼

複制

其原理是調用_OSI()這個預置方法,若傳入的字元串與作業系統核心名相比對則傳回1,If語句内容被執行。

字元串清單如下

作業系統 字元串
macOS "Darwin"
Linux(包括基于 Linux 核心的作業系統) "Linux"
FreeBSD "FreeBSD"
Windows "Windows 20XX"

見_osi--operating-system-interfaces-作業系統接口

舉個例子,假設我們的原方法為(順便一提,根據惠普筆記本-acel-裝置禁止這一節,我們需要将下面列出來的這個裝置禁用掉,我就順便拿來舉例了)

Device (ACEL)
{
    Name (_HID, EisaId ("HPQ6007"))  // _HID: Hardware ID
...
    Method (_STA, 0, NotSerialized)  // _STA: Status
    {
        If (LEqual (^^LPCB.EC0.ECOK, One))
        {
            If (LEqual (DVPN, 0xFF))
            {
                Store (0x0F, Local0)
                Store (^^LPCB.EC0.SMRD (0xC7, 0x50, 0x0F, RefOf (Local1)), Local2)
                If (LOr (LNotEqual (Local1, 0x33), LNotEqual (Local2, Zero)))
                {
                    Store (Zero, Local0
                }
                Store (Local0, DVPN)
            }
        }
        Return (DVPN)
    }
...
}
           

全選代碼

複制

我們可以将原來的_STA()方法重命名為XSTA(),然後用我們自己寫的方法代替它。

Scope(_SB.PCI0.ACEL) {
        Method (_STA, 0, NotSerialized) {
            If (_OSI("Darwin")) {
                Return (0)
            }
            Else {
                Return(XSTA())
            }
        }
    }
           

全選代碼

複制

這一個方法的意思是:如果作業系統為macOS,則傳回0(禁用),否則調用原方法(XSTA)。如果是無傳回值的方法則直接寫方法路徑名,不需要寫Return()。

關于_STA方法傳回值的詳細内容請參閱_sta-status-狀态

0x07 二進制更名

這一節中我們要用到MaciASL、Hackintool和Hex Fiend配合來确定某個方法在二進制代碼中如何進行替換。

首先我們用UPBI這個方法來舉例。

MaciASL進入原廠DSDT.dsl中搜尋UPBI

用一次實戰記錄電池熱更新檔的編寫方法

Hackintool 将 UPBI 轉換為十六進制

用一次實戰記錄電池熱更新檔的編寫方法

将此十六進制串複制下來在Hex Fiend中打開 DSDT.aml 搜尋

如圖,UPBI出現了兩次

用一次實戰記錄電池熱更新檔的編寫方法

我們可以根據其上下文來找到對應的二進制代碼

用一次實戰記錄電池熱更新檔的編寫方法

是以離這一段最近的UPBI即我們需要替換的内容。

用一次實戰記錄電池熱更新檔的編寫方法

得到十六進制串:55504249 00

使用Hackintool發現要把UPBI替換成XPBI,隻需要把55替換成58即可

用一次實戰記錄電池熱更新檔的編寫方法

故完整的替換方法為:

[BATT]UPBI to XPBI
Find:    55504249 00
Replace: 55504249 00
           

全選代碼

複制

其他的方法也這樣替換。

需要注意的是,對于要替換的_SB.PCI0.ACEL._STA方法,由于_STA方法涉及的覆寫面太廣,幾乎每個裝置都有自己的_STA方法,是以我們需要在Hex Fiend中擷取更多的二進制代碼,來唯一确定這個_STA方法。

如果你擷取到的二進制代碼在Hex Fiend中隻能搜尋到一處,正确的地方,那就可以放心地填入OpenCore/Clover,否則一定要擷取更多的二進制代碼。

最終我們擷取到的替換方法為

[BATT]ACEL._STA to XSTA
Find:    055F5354 4100A040
Replace: 05585354 4100A040
           

全選代碼

複制

0x08 處理External

External語句類似于C中的#include或者java中的import

如果你現在點選編譯的話會出現這樣的報錯

用一次實戰記錄電池熱更新檔的編寫方法

我們需要加上External語句來告訴編譯器我們引用的這些東西是在别的檔案裡面的

語句的格式大概是

External (_SB.PCI0.LPCB.EC0.XMRD, MethodObj)
           

全選代碼

複制

第一個參數是路徑名字,第二個參數是對象的類型,要根據原文的類型來做判斷。

常見的類型有IntObj、FieldUnitObj、MutexObj、MethodObj、DeviceObj、PkgObj等,請參考添加外部引用聲明

0x09 檢查mutex是否已經置0

EOF

至此,我們的電池更新檔終于完工了!

點選 File - Save As 另存為 aml 格式并且放到 OC/ACPI 檔案夾中或者 Clover/ACPI/Patched 檔案夾中,然後修改配置檔案加載,并且将應該改名的方法全部改名,重新開機一下你就會看到,你的電池電量出現啦!

文筆拙劣,隻能以自己的經驗給出一些過程。

歡迎在評論區留言交流。