如今,很難找到有關S3PI的可靠信息和真正的幫助,如果您嘗試使用S3PI作為庫來與SIMS軟件包文件進行交互,則更是如此。此外,S3PI庫不在github上,這裡沒有存儲庫。
由於這些原因,我決定創建此存儲庫以整體存儲S3PI庫,並提供有關如何正確使用它的代碼示例和提示。此外,我在這裡彙編了與S3PI相關的Internet上發現的所有有用信息!
如果您想貢獻更多信息,請創建拉動請求或通過“問題”選項卡進行報告。我將盡力保留盡可能多的信息和示例,但是如果S3PI的作者要求它,我將脫機此存儲庫。
重要的
記住,創建S3PI圖書館的所有學分都可以轉到令人難以置信的“ Peter L Jones”!
“ SIMS 3軟件包接口”提供了“理解” SIMS3遊戲軟件包的便攜式代碼的核心庫。請注意,(一些次要調整)核心庫代碼還了解其他遊戲程序包格式(例如Simcity Online,SIMS4)。
與核心庫一起,還有許多提供項目主要部分的“包裝器”。這些處理包裝中數據(或任何其他源)中數據的序列化和序列化。
進一步的工具可以使用此庫和包裝器來操縱SIMS遊戲的軟件包文件的數據內容。
請注意,此圖書館的開發以及此處提供的包裝紙現已完成。
“ S3PI”是“ SIMS3™軟件包接口”的首字母縮寫詞。它提供了支持,以訪問電子Arts SIMS3™遊戲使用的單個“軟件包”文件中的數據。 “軟件包”是一個通常“ .package”的文件擴展名的磁盤文件(但使用其他擴展名)。但是,S3PI使用的主要識別功能是文件開頭的四個字節魔法曲奇,必須是“ DBPF”。此外,對於SIMS 3™,文件格式版本編號必須為2(SIMS City 5™,幾乎不支持SIMS City 5™)。
請注意,不支持“受保護的”軟件包(帶有“ DBPP”的魔法曲奇)。請注意,當前不支持“臨時”軟件包(帶有“ DBBF”的魔法曲奇)。
S3PI是一組.NET組件(以下記錄並在此處可用),該組件實現了Sims 3 Wiki的描述(此處可作為Wiki-Mirror提供)。請注意,如果您不確定某些事情應該如何工作,或者您認為您在S3PI中發現問題,這總是值得檢查的,這仍然很可能。 Wiki和圖書館都不是正確的 - 實際上,兩者都以不同的方式是錯誤的。
S3PI庫帶有一個編譯的幫助文件(.CHM),該文件描述了您需要知道的許多內容。您也可以在此處訪問此版本。此頁面試圖快速概述,因為庫非常廣泛,並且可以弄清楚您需要知道的內容可能很棘手。
此頁面分為剩下的三個部分。
一旦您了解瞭如何使用包裝器訪問軟件包中的數據,則應查閱SIMS3 Wiki的文件類型列表,以查看受支持的文件以及如何獲得支持(如果不是S3PI分發的一部分)。
提示
如果您有未解決的問題,請隨時在論壇/討論中發布。很高興提供幫助!
該庫是一組.NET組件DLL。如果您可能使用庫從一個以上的項目進行工作,我強烈建議將它們放在與項目和解決方案工作區分開的文件夾中 - 但在您的文件夾結構中附近。這樣,不必更新每個項目就很容易替換它們。
要使用新項目中的庫,您首先需要知道要使用的庫中哪些部分。我故意將依賴性部件分開,以便您的項目只需要引用所需的DLL,從而使您的項目規模降低。缺點是有很多DLL!
要注意的第一件事是,其中許多是“包裝紙” - 任何名字都像SomethingResource.DLL 。這些包含代碼,以了解SIMS 3軟件包文件中一個或多個資源的內容。
首先,讓我們看一下每個剩餘的組件。之後,我將提供有關如何啟動新項目的概述。我會以一些關於組織您的工作實踐的想法。
筆記
在分發代碼之前,您必須同意GPLV3許可證。請注意,當您鏈接到GPLV3庫時,您本質上是重新使用它,並且必須按照等效的條款分發代碼。您可以在免費軟件基金會網站上找到更多詳細信息。
何時包含它:總是 - 圖書館的其他幾個部分都需要。
摘要:包含許多與“ Sims 3”直接相關的類,並且沒有對任何s3pi.Interfaces類型的引用。可以想像,它們可以用於不使用其餘S3PI庫的項目(因此將其分開)。
進一步閱讀:通過引用此組件,您將獲得以下內容...
System.Collections.Generic.AHandlerList<T> - List<T>通過提供的EventHandler提供列表更新的反饋。System.ArgumentLengthException表示參數長度的錯誤。System.Extensions LINQ未提供的有用擴展方法(並且沒有延期執行)。 (班級名稱可疑,可能會改變...)System.Security.Cryptography.FNV32 -FNV32哈希例程System.Security.Cryptography.FNV64 -FNV64哈希例程System.Text.SevenBitString在從或到Stream的給定Encoding中,讀寫一個七位的編碼長度排名的字符串。System.Security.Cryptography.Sims3PackCRC計算存儲在sims3pack文件中的數據塊的CRC。 (好吧,可以說這沒有理由在這裡,但是就代碼依賴而言,這是有道理的!) 何時包括:總是。
摘要:最初旨在允許通過替換DLL更改庫的各種設置。這從未發生過。
進一步閱讀:什麼都沒有。
何時包括:總是。圖書館本身不僅需要它,而且定義了公共API。
摘要:提供了整個庫和包裝器中使用的許多接口,抽像類和輔助類別,這些類別定義了各種庫類提供的公共方法。
進一步閱讀:
s3pi.Interfaces名稱空間文檔。請注意,名稱空間是由s3pi.GenericRCOL類“污染”的。純粹主義者也可能會考慮一些助手類“污染” ...
何時包含它:使用SIMS 3軟件包文件時。
摘要:提供s3pi.Interfaces中定義的抽像類和接口的具體實現。
進一步閱讀:什麼都沒有。
何時包含它:使用資源包裝器時。
摘要:如果您正在創建新的資源或從軟件包中讀取資源,這是推薦的機制。但是,確實存在替代方案。
進一步閱讀:
s3pi.WrapperDealer.WrapperDealer負責將IResourceIndexEntry中的ResourceType與理解它或默認包裝器的特定類(A wrapper')關聯。 s3pi.DefaultResource和s3pi.GenericRCOLResource提供了一旦參考的基礎,以了解如何使用資源。
何時包括:如果您想要社區標准文件名。
摘要:在《模擬人生3》開始時,修改社區同意在包裝文件外部如何命名包裝資源的設定格式。該組件為S3PI庫提供了實現。
進一步讀取: s3pi.Extensions名稱空間。圖書館的該區域仍然需要正確記錄。
何時包含它:當您需要CopyAbleMessageBox(或ISSEATEXCEPTION)時。
摘要:此組件提供了一種顯示消息的方法,使用戶可以輕鬆複製內容。將來,其他一般控制可能會出現在這裡。
進一步讀取: CopyableMessageBox類, CopyableMessageBoxButtons枚舉和CopyableMessageBoxIcon枚舉。
何時包括:當您想要一個自定義控件之一時。
摘要:此組件提供了與S3PI中數據類型相關的自定義控件。
進一步閱讀: ResourceTypeCombo類, TGIBlockCombo類和TGIBlockListEditor類。圖書館的該區域仍然需要正確記錄。
何時包含它:當您使用DDS映像並想要一種在Winforms應用程序中顯示它們的方法。
摘要:此組件提供了自定義控件和DDS資源支持。
進一步閱讀: DDSPanel類和DDSPanel.MaskChannel枚舉。
何時包含它:在為S3PE編寫助手時可能很有用。
摘要:提供將資源映射到可能對此類資源感興趣的一個或多個程序的支持。
進一步閱讀: s3pi.Helpers名稱空間。圖書館的該區域仍然需要正確記錄。
基於上一節System.Custom , s3pi.Settings , s3pi.Interfaces等設置您需要的引用。此外,您將需要考慮是否要參考特定的包裝程序集。在大多數情況下,這將是適當的方法。默認情況下,您的引用組件將與您的程序一起復製到項目輸出文件夾。
請注意,還有某些其他組件,例如配置文件,您還需要安排項目的構建(如果更新)到輸出文件夾。
您可能想查看S3OC和S3PE Visual Studio Solutions,以了解我的完成方式。
S3PI提供了許多C#類,以協助想要訪問SIMS 3個軟件包文件以及存儲在其中的資源的程序。 “核心庫”僅了解包裝容器本身 - 它對資源的內容不了解。那是委派給“包裝紙”的。包裝器通過聲明其支持的ResourceTypes清單與資源相關聯。核心庫將學術資源類的實例返回到庫“客戶端”。
因此,包裝器具有兩個主要目的:提供對一種或多種資源類型的內容的“理解”,並讓核心庫知道他們理解的哪些資源類型。包裝器可以在認為合適的情況下提供一個或多個資源處理程序,但鼓勵作者將不同的擔憂分為不同的包裝器。
核心庫通過在S3PI庫文件夾中的組件中搜索具有適當接口的人來標識包裝器。
WrapperDealer具有一個接口,可讓客戶端應用程序啟用和禁用ResourceType和Warpper的特定組合。
我現在在S3PI庫文檔中包括了DefaultResource 。
最簡單的示例是源分佈中的DefaultResource中給出的示例。它“無所作為”超出對任何包裝器的必要意義。
它定義了兩個類:
public class DefaultResource : AResource { }
public class DefaultResourceHandler : AResourceHandler { }包含包裝器的組件必須包含實現AResourceHandler類。此類提供IDictionary<Type, List<string>>在實現AResource類和包含ResourceType值的字符串列表之間查找。 (所使用的字符串是從ResourceType TypedValue到字符串的值,即十六進製字符串。)
DefaultResource使用“*”,讓WrapperDealer知道拿走一切很樂意。不要在包裝紙中使用它!
然後, DefaultResource 。
強烈建議您使用const Int32 recommendedApiVersion = 1;在班級的頂部,以使以後的兼容性。如果需要,這確實允許您“版本”包裝器的API。不過,在實踐中可能沒有那麼有用。
您必須擁有屬性AApiVersionedFields RecommendedApiVersion 。
DefaultResource的構造函數證明了一個重要的一點 - 新資源是不了解的。它可以通過檢查傳遞給構造函數的流是否為空,可以檢查它是新的。然後,它應該創建一個MemoryStream ,並使用資源的最小有效數據內容填充它。
就是這樣!您無需做任何其他事情。當然,您還沒有為任何人提供使用包裝紙的理由...
筆記
經過一番思考, ImageResource和TextResource被認為是貶低的。但是,在可預見的將來,它們將留在圖書館。儘管下面寫了什麼,但我不再認為擁有一個僅是字節流的資源的包裝器是一個好主意。
DDS資源採用的方法被認為更正確。下面直接指出的是:此處的設計受S3PE的影響,而不是針對數據本身的結構實施任何內容,這是包裝器的目的。這應該以類似於DDS資源的方式處理。
進一步的推出是在質量檢查週期內的添加,然後去除_VID資源包裝器,當時發現內容純粹是自定義電子藝術視聽編解碼器的輸出,而不是具有任何可有效的解密內容。
總之:“值”應該只是一個字符串。通常,內置格式化器會從您的公共物業中構建合適的建築。
Imageresource是一個稍微複雜的例子。它堅持兩個類模型:
public class ImageResource : AResource { }
public class ImageResourceHandler : AResourceHandler { }此包裝器處理圖像 - 許多不同的資源類型只是存儲的PNG89文件。
ImageResourceHandler的靜態構造函數讀取了所有已知圖像資源類型的列表(來自彙編所在的文件夾中的文件)。然後,它用於填充列表IDictionary<Type, List<string>>實例構造函數中的列表,然後由WrapperDealer使用。這意味著將新的資源類型添加到該包裝支持的列表中很容易 - 只需編輯文本文件並添加它們,而無需重新編譯即可。 (這是一個很好的模式,我應該使重複使用更容易...)
ImageResource類仍然相當簡單。它的構造函數可確保如果通過了空流,則有一個有效的PNG89圖像。它提供了一個稱為“ value”的單個屬性,該屬性返回從PNG89數據創建的System.Drawing.Image 。它不支持將圖像保存回現有資源。
值得進一步提及的Value屬性 - S3PI演示前端(現在稱為S3PE)檢查資源是否具有Value屬性,如果是的,則是否具有圖像或字符串數據。它知道如何展示這兩個(目前)。返回適當的一個對於調試可能很有用。
您會注意到流到使用前將流定位設置為零 - 始終假設它處於未知狀態。
TextResource包裝器非常相似 - 文件中定義了文本資源;它具有“值”屬性,該屬性將資源作為字符串值返回。此外,它具有一些特定於文本的屬性,包括將數據訪問為XML。 (將XML包裝器作為文本包裝器的擴展程序,並且僅處理已知的XML文件是更好的設計...)
該包裝器處理單個資源類型 - 軟件包名稱映射。它提供了IDictionary<ulong, string>接口,允許閱讀和更新地圖。這是如何處理更新的一個非常簡單的示例。
這是它的工作方式。這是一個簡單的示例,但模式可以擴展到更複雜的需求。
這項工作是在許多地方完成的:
Stream屬性檢查資源是否已被污垢(即更改)。如果是這樣,它會丟棄當前流並調用UnParse() 。
實例構造函數檢查空流,並調用UnParse()以構建最小有效資源。
Parse(Stream s)方法將數據讀取到用於操縱它的數據結構中 - 在這裡用Dictionary<ulong, string> 。由於這是允許插入數據的不同長度條目的重複結構,因此在流中使用數據並不有效。
UnParse()方法將數據結構導出回新流,在需要時創建新對象。
如上所述, Stream屬性需要知道資源是否已被弄髒。 IDictionary<ulong, string>接口的實現通過致電OnResourceChanged(this, EventArgs.Empty)來解決此問題。這是在AResource上提供的,並將資源設置為Dirty,並撥打了聆聽該活動的任何內容的ResourceChanged處理程序。
Catalogresource確實將這些想法進一步提高了。它具有用於一組相關資源的抽像類。我試圖保持一致的編碼模式,以幫助了解正在發生的事情。我將其作為練習,向讀者進行實施,以了解所有類的交互方式。希望這應該有意義! (如果沒有,當錯誤出現時,我會很難!!)
這具有最近的好處,儘管相對簡單,但確實有一些有趣的位。它也是我在編寫新的包裝紙中最經常諮詢自己的包裝紙之一。
RCOL資源是一種普通資源,如上所述 - 基本差異是它是其他“資源”的容器,稱為RCOL塊。每個塊的格式由四個字符代碼(“ fourcc”或tag)識別;這些塊還具有資源類型。 RCOL資源(在軟件包中)具有與(資源中)中的第一個RCOL塊相同的類型,並且該資源以第一個RCOL塊命名。一些RCOL資源僅包含一個RCOL塊;其他包含多個RCOL塊。
基本支持由s3pi.GenericRCOLResource提供。除了讀取塊並將塊寫入軟件包外,資源包裝器還具有支持RCOL格式的其他方法,並且還有RCOL塊處理程序的註冊表。
(請注意,術語“塊”寬鬆地用於參考RCOL塊...)
有一個抽像類, ARCOLBlock ,它定義了基本面。有一個默認的實現, DefaultRCOL ,因為當註冊表中未定義其他匹配的RCOL塊處理程序時,可以提供最小的支持。
除了擴展ARCOLBlock而不是AResource之外,編寫RCOL塊處理程序的任務與編寫資源包裝器非常相似。
但是,電子藝術 /馬克西斯最近開始使生活更加艱難,因為某些單rcol容器並不完全符合對容器應如何工作的原始理解。在閱讀代碼或自己編寫時要注意這一點。
首先,您需要有經驗,並且已經知道如何使用S3PE軟件編輯SIMS軟件包文件。 S3PE基本上是S3PI的圖形接口,可以使用普通用戶。因此,可以肯定地說S3PI能夠做S3PE可以做的一切,甚至更多。
筆記
S3PE也可以在此存儲庫中下載。其可執行文件及其C#源代碼和Visual Studio解決方案均包括。
知道這一點,要在程序中實現S3PI,您需要代碼,就好像您的程序使用S3PE執行操作一樣。
假設您要從包文件中刪除某個資源。如果使用S3PE,則首先打開該軟件包文件,搜索刪除資源。然後,您將按DEL鍵刪除該文件,然後保存。包裝文件保存後,您將關閉它。
如果您使用S3PE執行此操作,則如果您使用S3PI執行相同的操作,則您的程序應執行相同的步驟。請參閱下面的示例代碼,執行刪除資源的操作“ 0x00B2D882-0X000000000000-0X0A12300000FF0000”,以示例...
using s3pi ;
using s3pi . Interfaces ;
using s3pi . Package ;
//Open the package
IPackage package = Package . OpenPackage ( 0 , "C:/Folder/someFile.package" , true ) ;
//Search the resource "0x00B2D882-0x00000000-0x0A12300000FF0000" inside the package
foreach ( IResourceIndexEntry item in package . GetResourceList )
{
//Get current entrie resource TGI
string typeHex = GetLongConvertedToHexStr ( item . ResourceType , 8 ) ;
string groupHex = GetLongConvertedToHexStr ( item . ResourceGroup , 8 ) ;
string instanceHex = GetLongConvertedToHexStr ( item . Instance , 16 ) ;
//If is the target resource, delete it
if ( typeHex == "0x00B2D882" && groupHex == "0x00000000" && instanceHex == "0x0A12300000FF0000" )
package . DeleteResource ( item ) ;
}
//Save the changes
package . SavePackage ( ) ;
//Close the package
Package . ClosePackage ( 0 , package ) ; 重要的
完成處理後,始終關閉打開的包裝文件非常重要。
筆記
Dream Launcher是由“ Marcos4503”創建的《模擬人生3》的啟動器。 Dream Launcher利用S3PI來實現各種功能,例如合併軟件包,清理儲蓄和其他功能。您可以按照此鏈接查看Dream Launcher存儲庫,並查看源代碼以獲取更多S3PI使用示例。 Dream Launcher是使用C#作為編程語言創建的。
using s3pi ;
using s3pi . Interfaces ;
using s3pi . Package ;
//Creates a new Package that will receive the resources from 2 other Packages
IPackage finalPackage = Package . NewPackage ( 0 ) ;
//Open the Package 1 and copy all resources to the final package
IPackage package1 = Package . OpenPackage ( 0 , "C:/Folder/package1.package" , false ) ;
foreach ( IResourceIndexEntry item in package1 . GetResourceList )
finalPackage . AddResource ( item , ( package1 as APackage ) . GetResource ( item ) , true ) ;
Package . ClosePackage ( 0 , package1 ) ;
//Open the Package 2 and copy all resources to the final package
IPackage package2 = Package . OpenPackage ( 0 , "C:/Folder/package2.package" , false ) ;
foreach ( IResourceIndexEntry item in package2 . GetResourceList )
finalPackage . AddResource ( item , ( package2 as APackage ) . GetResource ( item ) , true ) ;
Package . ClosePackage ( 0 , package2 ) ;
//Enable compression for all viable resources of final merged package (the same way S3PE does)
foreach ( IResourceIndexEntry item in finalPackage . GetResourceList )
item . Compressed = ( ushort ) ( ( item . Filesize != item . Memsize ) ? 0xFFFF : 0x0000 ) ;
//Saves the final Package, result of the merge
finalPackage . SaveAs ( "C:/Folder/finalFile.package" ) ;
Package . ClosePackage ( 0 , finalPackage ) ; using s3pi ;
using s3pi . Interfaces ;
using s3pi . Package ;
//Open a .nhd save file
IPackage nhdSaveFile = Package . OpenPackage ( 0 , "C:/Folder/saveFile.nhd" , false ) ;
//Search inside the package, by the first thumbnail of type "SNAP" (or hex type "0x6B6D837E")
foreach ( IResourceIndexEntry item in nhdSaveFile . GetResourceList )
if ( GetLongConvertedToHexStr ( item . ResourceType , 8 ) == "0x6B6D837E" )
{
//Get the base stream for this resource
Stream aPackageStream = ( nhdSaveFile as APackage ) . GetResource ( item ) ;
//Get the base resource using the "ImageResource" s3pi wrapper***
IResource baseResource = ( IResource ) ( new ImageResource . ImageResource ( 0 , aPackageStream ) ) ;
//Get the bitmap from base resource stream
BitmapImage bitmapImage = new BitmapImage ( ) ;
bitmapImage . BeginInit ( ) ;
bitmapImage . StreamSource = baseResource . Stream ;
bitmapImage . CacheOption = BitmapCacheOption . OnLoad ;
bitmapImage . EndInit ( ) ;
bitmapImage . Freeze ( ) ;
//... continue ...//
//Cancel the search
break ;
}
//Close the save file
Package . ClosePackage ( 0 , nhdSaveFile ) ; ***請注意,在這裡,我們可以使用WrapperDealer類,以便S3PI自動為我們使用的資源提供正確的包裝器。如果我們在那裡使用WrapperDealer ,S3PI會自動為我們帶來ImageResource包裝器,但是,眾所周知, WrapperDealer與某些.NET框架(例如WPF本身)不相容,從而導致崩潰。因此,當使用包裝文件中的資源時,始終建議直接使用包裝器。如果我們選擇使用WrapperDealer類獲取圖像,那麼代碼段將看起來像這樣...
//...
//Get the resource using WrapperDealer
IResource resource = WrapperDealer . GetResource ( 0 , nhdSaveFile , item , true ) ;
//Get the bitmap from base resource stream
BitmapImage bitmapImage = new BitmapImage ( ) ;
bitmapImage . BeginInit ( ) ;
bitmapImage . StreamSource = resource . Stream ;
bitmapImage . CacheOption = BitmapCacheOption . OnLoad ;
bitmapImage . EndInit ( ) ;
bitmapImage . Freeze ( ) ;
//... using s3pi ;
using s3pi . Interfaces ;
using s3pi . Package ;
//Open a package that contains a CASP resource
IPackage openedPackage = Package . OpenPackage ( 0 , "C:/Folder/clothes.package" , true ) ;
//Search the first CASP (or hex type 0x034AEECB) resource inside the package
foreach ( IResourceIndexEntry item in openedPackage . GetResourceList )
if ( GetLongConvertedToHexStr ( item . ResourceType , 8 ) == "0x034AEECB" )
{
//Get the CASP stream
Stream caspStream = WrapperDealer . GetResource ( 1 , openedPackage , item , true ) . Stream ;
//Get the CASP resource
CASPartResource . CASPartResource sourceCASpart = new CASPartResource . CASPartResource ( 1 , caspStream ) ;
//Allow this CASP for Random Sims
sourceCaspart . ClothingCategory |= CASPartResource . ClothingCategoryFlags . ValidForRandom ;
//Disallow this CASP for Random Sims
sourceCaspart . ClothingCategory &= ~ CASPartResource . ClothingCategoryFlags . ValidForRandom ;
//Delete the old CASP resource
openedPackage . DeleteResource ( item ) ;
//Add the new modified resource
openedPackage . AddResource ( ( ( IResourceKey ) item ) , ( ( AResource ) sourceCaspart ) . Stream , true ) ;
//Release streams
caspStream . Dispose ( ) ;
caspStream . Close ( ) ;
( ( AResource ) sourceCaspart ) . Stream . Dispose ( ) ;
( ( AResource ) sourceCaspart ) . Stream . Close ( ) ;
}
//Save the package and close it
openedPackage . SavePackage ( ) ;
Package . ClosePackage ( 0 , openedPackage ) ; 筆記
在這裡,我們使用WrapperDealer訪問CASP資源,但是我們也可以直接使用包裝器CASPartResource訪問CASP資源。
您可能已經了解,SIMS軟件包文件就像一個“ zip文件”,其中包含其中的其他幾個文件。軟件包文件中的每個文件/資源都具有與之關聯的TGI。
TGI基本上是類型,組和實例。類型和組為8位十六進制,而實例是16位十六位。為了避免遊戲加載時資源之間的衝突,遊戲加載的所有軟件包中存在的每個資源都必須具有唯一的TGI組合。
當您使用S3PE打開軟件包文件時,您可以輕鬆地看到打開軟件包中每個資源的類型,組和實例。現在,您已經知道了這一點,請記住,在編輯SIMS軟件包文件時,您應始終確保插入包裝文件中的資源必須始終具有唯一的TGI。
如果您對此有任何進一步的疑問,則可以閱讀本文,其中談論並很好地解釋了Mod的衝突,它們是如何發生的,如何解決這些衝突等等。
如果您閱讀了這麼遠,則應該對S3PI是什麼以及如何使用它有一個體面的了解。如果要訪問舊的官方S3PI存儲庫,則可以使用此鏈接。記住所有因創建S3PI庫的榮譽都是彼得·L·瓊斯(Peter L Jones)。
由Marcos Tomaz創建的存儲庫