当前位置: 首页 > news >正文

NTLDR源代码分析之从GetSector函数到blread函数

NTLDR源代码分析之从GetSector函数到blread函数

;++
;
; Name:
;
;       GetSector
;
; Description:
;
;       Reads the requested number of sectors from the specified drive into
;       the specified buffer.
;
; Arguments:
;
;             ULONG Virtual address into which to read data
;             ULONG Number of sectors to read
;             ULONG Physical sector number
;             ULONG Drive Number
;             ULONG Function Number
;     TOS ->  ULONG Flat return address (must be used with KeCodeSelector)
;
;--

EXPORT_ENTRY_MACRO    GetSector
;
; Move the arguments from the caller's 32bit stack to the SU module's
; 16bit stack.
;

        MAKE_STACK_FRAME_MACRO  <GetSectorFrame>, ebx

;
; Go into real mode. We still have the same stack and sp
; but we'll be executing in realmode.
;

        ENTER_REALMODE_MACRO

ARC_STATUS
XferPhysicalDiskSectors(
IN  UCHAR     Int13UnitNumber,
IN  ULONGLONG StartSector,
IN  UCHAR     SectorCount,
IN  PUCHAR    Buffer,
IN  UCHAR     SectorsPerTrack,
IN  USHORT    Heads,
IN  USHORT    Cylinders,
IN  BOOLEAN   AllowExtendedInt13,
IN  BOOLEAN   Write
)
{


//
// Check to see whether the cylinder is addressable via conventional int13.
//
if(cylinder >= Cylinders) {

        //
// First try standard int13.
// Some BIOSes (Thinkpad 600) misreport the disk size and ext int13.
// So let's get this case out of the way now by trying the read anyway.
//

        if( cylinder == Cylinders ) {
if( cylinder <= 1023 ) {

//
// Give conventional int13 a shot.
//

s = GET_SECTOR(
(UCHAR)(Write ? 3 : 2),     // int13 function number
Int13UnitNumber,
head,
(USHORT)cylinder,           // we know it's 0-1023
sector,
SectorCount,
Buffer
);

ARC_STATUS
XferPhysicalDiskSectors(
IN  UCHAR     Int13UnitNumber,
IN  ULONGLONG StartSector,
IN  UCHAR     SectorCount,
OUT PUCHAR    Buffer,
IN  UCHAR     SectorsPerTrack,
IN  USHORT    Heads,
IN  USHORT    Cylinders,
IN  BOOLEAN   AllowExtendedInt13,
IN  BOOLEAN   Write
);

#define ReadPhysicalSectors(d,a,n,p,s,h,c,f)                                \
\
XferPhysicalDiskSectors((d),(a),(n),(p),(s),(h),(c),(f),FALSE)

#define WritePhysicalSectors(d,a,n,p,s,h,c,f)                               \
\
XferPhysicalDiskSectors((d),(a),(n),(p),(s),(h),(c),(f),TRUE)


chenghao@chenghaodeiMac base % grep "ReadPhysicalSectors" -nr ./boot |grep -v "inary"
./boot/lib/i386/biosdrv.c:1508:        if(ReadPhysicalSectors((UCHAR)DriveId,0,1,Buffer,1,1,1,FALSE)) {
./boot/lib/i386/biosdrv.c:1689:    Status = ReadPhysicalSectors(
./boot/lib/i386/biosdrv.c:1814:        Status = ReadPhysicalSectors(
./boot/lib/i386/biosdrv.c:2219:            Status = ReadPhysicalSectors(Int13Unit,
./boot/lib/i386/bootx86.h:111:#define ReadPhysicalSectors(d,a,n,p,s,h,c,f)                                \
./boot/efi/ia64/bootia64.h:216:#define ReadPhysicalSectors(d,a,n,p,s,h,c,f)   

ARC_STATUS
BiosDiskOpen(
IN ULONG DriveId,
IN OPEN_MODE OpenMode,
OUT PULONG FileId
)
{


ARC_STATUS
BiospWritePartialSector(
IN UCHAR Int13Unit,
IN ULONGLONG Sector,
IN PUCHAR Buffer,
IN BOOLEAN IsHead,
IN ULONG Bytes,
IN UCHAR SectorsPerTrack,
IN USHORT Heads,
IN USHORT Cylinders,
IN BOOLEAN AllowXInt13
)
{
ARC_STATUS Status;

    //
// Read sector into the write buffer
//
Status =  (
Int13Unit,
Sector,
1,
FwDiskCache,
SectorsPerTrack,
Heads,
Cylinders,
AllowXInt13
);

ARC_STATUS
BiosDiskWrite(
IN ULONG FileId,
OUT PVOID Buffer,
IN ULONG Length,
OUT PULONG Count
)
{

    //
// Special case of transfer occuring entirely within one sector
//
CurrentSector = HeadSector;
if(HeadOffset && TailByteCount && (HeadSector == TailSector)) {

        Status = ReadPhysicalSectors(
Int13Unit,
CurrentSector,
1,
FwDiskCache,
SectorsPerTrack,
Heads,
Cylinders,
AllowXInt13
);

ARC_STATUS
pBiosDiskReadWorker(
IN  ULONG   FileId,
OUT PVOID   Buffer,
IN  ULONG   Length,
OUT PULONG  Count,
IN  USHORT  SectorSize,
IN  BOOLEAN xInt13
)
{


//
// Perform the read.
//

        if(xInt13) {
Status = ReadExtendedPhysicalSectors(Int13Unit,
CurrentSector,
SectorsToTransfer,
pTransferDest);
} else {
Status = ReadPhysicalSectors(Int13Unit,
CurrentSector,
SectorsToTransfer,
pTransferDest,
SectorsPerTrack,
Heads,
Cylinders,
AllowXInt13);
}

BL_DEVICE_ENTRY_TABLE BiosDiskEntryTable =
{
(PARC_CLOSE_ROUTINE)BiosDiskClose,
(PARC_MOUNT_ROUTINE)BlArcNotYetImplemented,
(PARC_OPEN_ROUTINE)BiosDiskOpen,
(PARC_READ_ROUTINE)BiosDiskRead,
(PARC_READ_STATUS_ROUTINE)BlArcNotYetImplemented,
(PARC_SEEK_ROUTINE)BiosPartitionSeek,
(PARC_WRITE_ROUTINE)BiosDiskWrite,
(PARC_GET_FILE_INFO_ROUTINE)BiosDiskGetFileInfo,
(PARC_SET_FILE_INFO_ROUTINE)BlArcNotYetImplemented,
(PRENAME_ROUTINE)BlArcNotYetImplemented,
(PARC_GET_DIRECTORY_ENTRY_ROUTINE)BlArcNotYetImplemented,
(PBOOTFS_INFO)NULL
};

ARC_STATUS
BiosPartitionOpen(
IN PCHAR OpenPath,
IN OPEN_MODE OpenMode,
OUT PULONG FileId
)
{

    //
// If we're opening a floppy drive, there are no partitions
// so we can just return the physical device.
//

    if((_stricmp(OpenPath,"multi(0)disk(0)fdisk(0)partition(0)") == 0) ||
(_stricmp(OpenPath,"eisa(0)disk(0)fdisk(0)partition(0)" ) == 0))
{
return(BiosDiskOpen( 0, 0, FileId));
}
if((_stricmp(OpenPath,"multi(0)disk(0)fdisk(1)partition(0)") == 0) ||
(_stricmp(OpenPath,"eisa(0)disk(0)fdisk(1)partition(0)" ) == 0))
{
return(BiosDiskOpen( 1, 0, FileId));
}


./boot/lib/i386/arcemul.c:1129:        Status = BiosPartitionOpen( OpenPath,
./boot/lib/i386/arcemul.c:2518:            to BiosPartitionOpen.  The name it constructs may not be an
./boot/lib/i386/arcemul.c:2519:            actual partition.  BiosPartitionOpen is responsible for
./boot/lib/i386/arcemul.c:2524:            way this routine will construct a name that BiosPartitionOpen
./boot/lib/i386/arcemul.c:2643:        // ARC names for each successive partition until one BiosPartitionOpen
./boot/lib/i386/arcemul.c:2654:            Status = BiosPartitionOpen( ArcName,
./boot/lib/i386/biosdrv.c:126:        (PARC_OPEN_ROUTINE)BiosPartitionOpen,
./boot/lib/i386/biosdrv.c:248:BiosPartitionOpen(

ARC_STATUS
AEOpen(
IN PCHAR OpenPath,
IN OPEN_MODE OpenMode,
OUT PULONG FileId
)
{

    //
// Once a disk driver has been loaded we need to disable bios access to
// all drives to avoid mixing bios & driver i/o operations.
//

    if(AEBiosDisabled == FALSE) {
Status = BiosPartitionOpen( OpenPath,
OpenMode,
FileId );

        if (Status == ESUCCESS) {
return(ESUCCESS);
}
}


VOID
BlpTranslateDosToArc(
IN PCHAR DosName,
OUT PCHAR ArcName
)
{

        //
// Now we have to go through and count
// the partitions on the first drive.  We do this by just constructing
// ARC names for each successive partition until one BiosPartitionOpen
// call fails.
//

        PartitionCount = 0;
do {
++PartitionCount;
sprintf(ArcName,
"multi(0)disk(0)rdisk(0)partition(%d)",
PartitionCount+1);

            Status = BiosPartitionOpen( ArcName,
ArcOpenReadOnly,
&DriveId );

            if (Status==ESUCCESS) {
BiosPartitionClose(DriveId);
}
} while ( Status == ESUCCESS );

./boot/bldr/daytona_dbg/obj/i386/osloader_dbg.map:208: 0001:000050de       _AEOpen@12                 004060de f   boot:arcemul.obj
./boot/bldr/daytona/obj/i386/osloader.map:206: 0001:000050cf       _AEOpen@12                 004060cf f   boot:arcemul.obj
./boot/lib/i386/arcemul.c:172:AEOpen(
./boot/lib/i386/arcemul.c:723:    FIRMWARE_VECTOR_BLOCK->OpenRoutine               = AEOpen;
./boot/lib/i386/arcemul.c:1073:AEOpen(

VOID
BlFillInSystemParameters(
IN PBOOT_CONTEXT BootContextRecord
)
/*++

Routine Description:

    This routine fills in all the fields in the Global System Parameter Block
that it can.  This includes all the firmware vectors, the vendor-specific
information, and anything else that may come up.

Arguments:

    None.


Return Value:

    None.

--*/

{

    UNREFERENCED_PARAMETER( BootContextRecord );

    //
// Fill in the pointers to the firmware functions which we emulate.
// Those which we don't emulate are stubbed by BlArcNotYetImplemented,
// which will print an error message if it is accidentally called.
//

    FIRMWARE_VECTOR_BLOCK->LoadRoutine               = (PARC_LOAD_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->InvokeRoutine             = (PARC_INVOKE_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->ExecuteRoutine            = (PARC_EXECUTE_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->HaltRoutine               = (PARC_HALT_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->PowerDownRoutine          = (PARC_POWERDOWN_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->InteractiveModeRoutine    = (PARC_INTERACTIVE_MODE_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->AddChildRoutine           = (PARC_ADD_CHILD_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->SaveConfigurationRoutine  = (PARC_SAVE_CONFIGURATION_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->GetSystemIdRoutine        = (PARC_GET_SYSTEM_ID_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->MountRoutine              = (PARC_MOUNT_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->SetFileInformationRoutine = (PARC_SET_FILE_INFO_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->GetDirectoryEntryRoutine  = (PARC_GET_DIRECTORY_ENTRY_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->SetEnvironmentRoutine     = (PARC_SET_ENVIRONMENT_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->FlushAllCachesRoutine     = (PARC_FLUSH_ALL_CACHES_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->TestUnicodeCharacterRoutine = (PARC_TEST_UNICODE_CHARACTER_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->GetDisplayStatusRoutine   = (PARC_GET_DISPLAY_STATUS_ROUTINE)BlArcNotYetImplemented;
FIRMWARE_VECTOR_BLOCK->DeleteComponentRoutine    = (PARC_DELETE_COMPONENT_ROUTINE)BlArcNotYetImplemented;

    FIRMWARE_VECTOR_BLOCK->CloseRoutine              = AEClose;
FIRMWARE_VECTOR_BLOCK->OpenRoutine               = AEOpen;

    FIRMWARE_VECTOR_BLOCK->MemoryRoutine             = AEGetMemoryDescriptor;
FIRMWARE_VECTOR_BLOCK->SeekRoutine               = AESeek;
FIRMWARE_VECTOR_BLOCK->ReadRoutine               = AERead;
FIRMWARE_VECTOR_BLOCK->ReadStatusRoutine         = AEReadStatus;
FIRMWARE_VECTOR_BLOCK->WriteRoutine              = AEWrite;
FIRMWARE_VECTOR_BLOCK->GetFileInformationRoutine = AEGetFileInformation;
FIRMWARE_VECTOR_BLOCK->GetTimeRoutine            = AEGetTime;
FIRMWARE_VECTOR_BLOCK->GetRelativeTimeRoutine    = AEGetRelativeTime;

    FIRMWARE_VECTOR_BLOCK->GetPeerRoutine            = FwGetPeer;
FIRMWARE_VECTOR_BLOCK->GetChildRoutine           = FwGetChild;
FIRMWARE_VECTOR_BLOCK->GetParentRoutine          = AEGetParent;
FIRMWARE_VECTOR_BLOCK->GetComponentRoutine       = FwGetComponent;
FIRMWARE_VECTOR_BLOCK->GetDataRoutine            = AEGetConfigurationData;
FIRMWARE_VECTOR_BLOCK->GetEnvironmentRoutine     = AEGetEnvironment;

    FIRMWARE_VECTOR_BLOCK->RestartRoutine            = AEReboot;
FIRMWARE_VECTOR_BLOCK->RebootRoutine             = AEReboot;

}


//
// Fill in the TriageDump structure
//

    Status = BlOpen (DriveId, PAGEFILE_SYS, ArcOpenReadOnly, &PageFile);

./boot/lib/blio.c:338:_BlOpen (
./boot/lib/blio.c:599:BlOpen (


ARC_STATUS
_BlOpen (
IN ULONG DeviceId,
IN PCHAR OpenPath,
IN OPEN_MODE OpenMode,
OUT PULONG FileId
)
{


if( Status != ESUCCESS ) {
Status = (BlFileTable[Index].DeviceEntryTable->Open)(OpenPath,
OpenMode,
FileId);
}

chenghao@chenghaodeiMac base % grep "\->Open" -nr ./boot |grep -v "inary"
./boot/lib/blio.c:582:                Status = (BlFileTable[Index].DeviceEntryTable->Open)(OpenPath,
./boot/lib/i386/arcemul.c:723:    FIRMWARE_VECTOR_BLOCK->OpenRoutine               = AEOpen;


./boot/efi/arcemul.c:258:extern BL_FILE_TABLE BlFileTable[BL_FILE_TABLE_SIZE];

./boot/lib/bootlib.h:159:typedef struct _BL_FILE_TABLE {
./boot/lib/bootlib.h:182:} BL_FILE_TABLE, *PBL_FILE_TABLE;

typedef struct _BL_FILE_TABLE {
BL_FILE_FLAGS Flags;
ULONG DeviceId;
LARGE_INTEGER Position;
PVOID StructureContext;
PBL_DEVICE_ENTRY_TABLE DeviceEntryTable;
UCHAR FileNameLength;
CHAR FileName[MAXIMUM_FILE_NAME_LENGTH];
union {
NTFS_FILE_CONTEXT NtfsFileContext;
FAT_FILE_CONTEXT FatFileContext;
UDFS_FILE_CONTEXT UdfsFileContext;
CDFS_FILE_CONTEXT CdfsFileContext;
ETFS_FILE_CONTEXT EtfsFileContext;
NET_FILE_CONTEXT NetFileContext;
PARTITION_CONTEXT PartitionContext;
SERIAL_CONTEXT SerialContext;
DRIVE_CONTEXT DriveContext;
FLOPPY_CONTEXT FloppyContext;
KEYBOARD_CONTEXT KeyboardContext;
CONSOLE_CONTEXT ConsoleContext;
EFI_ARC_OPEN_CONTEXT EfiContext;        
} u;
} BL_FILE_TABLE, *PBL_FILE_TABLE;

./boot/inc/bldr.h:308:typedef struct _BL_DEVICE_ENTRY_TABLE {
./boot/inc/bldr.h:321:} BL_DEVICE_ENTRY_TABLE, *PBL_DEVICE_ENTRY_TABLE;


//
// Device entry table structure.
//

typedef struct _BL_DEVICE_ENTRY_TABLE {
PARC_CLOSE_ROUTINE Close;
PARC_MOUNT_ROUTINE Mount;
PARC_OPEN_ROUTINE Open;
PARC_READ_ROUTINE Read;
PARC_READ_STATUS_ROUTINE GetReadStatus;
PARC_SEEK_ROUTINE Seek;
PARC_WRITE_ROUTINE Write;
PARC_GET_FILE_INFO_ROUTINE GetFileInformation;
PARC_SET_FILE_INFO_ROUTINE SetFileInformation;
PRENAME_ROUTINE Rename;
PARC_GET_DIRECTORY_ENTRY_ROUTINE GetDirectoryEntry;
PBOOTFS_INFO BootFsInfo;
} BL_DEVICE_ENTRY_TABLE, *PBL_DEVICE_ENTRY_TABLE;


./boot/lib/ntfsboot.c:556:BL_DEVICE_ENTRY_TABLE NtfsDeviceEntryTable;
./boot/lib/ntfsboot.c:631:PBL_DEVICE_ENTRY_TABLE

BL_DEVICE_ENTRY_TABLE NtfsDeviceEntryTable;


//
//  We have finished initializing the structure context so now Initialize the
//  file entry table and return the address of the table.
//

    NtfsDeviceEntryTable.Open               = NtfsOpen;
NtfsDeviceEntryTable.Close              = NtfsClose;
NtfsDeviceEntryTable.Read               = NtfsRead;
NtfsDeviceEntryTable.Seek               = NtfsSeek;
NtfsDeviceEntryTable.Write              = NtfsWrite;
NtfsDeviceEntryTable.GetFileInformation = NtfsGetFileInformation;
NtfsDeviceEntryTable.SetFileInformation = NtfsSetFileInformation;
NtfsDeviceEntryTable.BootFsInfo = &NtfsBootFsInfo;

    return &NtfsDeviceEntryTable;
}


//
//  Local support routine
//

ARC_STATUS
NtfsReadNonresidentAttribute (
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
IN PCNTFS_ATTRIBUTE_CONTEXT AttributeContext,
IN VBO Vbo,
IN ULONG Length,
IN PVOID Buffer
)
{


ReadDisk( StructureContext->DeviceId, Lbo, ByteCount, &NtfsCompressedBuffer[i], bCacheNewData );
}


//
//  Low level disk read routines
//

//
//  VOID
//  ReadDisk (
//      IN ULONG DeviceId,
//      IN LONGLONG Lbo,
//      IN ULONG ByteCount,
//      IN OUT PVOID Buffer,
//      IN BOOLEAN CacheNewData
//      );
//

ARC_STATUS
NtfsReadDisk (
IN ULONG DeviceId,
IN LONGLONG Lbo,
IN ULONG ByteCount,
IN OUT PVOID Buffer,
IN BOOLEAN CacheNewData
);

#define ReadDisk(A,B,C,D,E) { ARC_STATUS _s;                     \
if ((_s = NtfsReadDisk(A,B,C,D,E)) != ESUCCESS) {return _s;} \
}


//
//  Local support routine
//

ARC_STATUS
NtfsReadDisk (
IN ULONG DeviceId,
IN LONGLONG Lbo,
IN ULONG ByteCount,
IN OUT PVOID Buffer,
IN BOOLEAN CacheNewData
)
{


//
//  Issue the read through the cache.
//

    Status = BlDiskCacheRead(DeviceId, 
(PLARGE_INTEGER)&Lbo, 
Buffer, 
ByteCount, 
&i,
CacheNewData); 

./boot/inc/blcache.h:225:BlDiskCacheRead (

./boot/lib/blcache.c:541:BlDiskCacheRead (

ARC_STATUS
BlDiskCacheRead (
ULONG DeviceId,
PLARGE_INTEGER pOffset,
PVOID Buffer,
ULONG Length,
PULONG pCount,
BOOLEAN CacheNewData
)
{

                if (ArcSeek(DeviceId,
&LIReadOffset,
SeekAbsolute) != ESUCCESS)
{
BlDiskCacheFreeRangeEntry(pNewCacheEntry);
goto SkipCache;
}

                    if (ArcRead(DeviceId,
pNewCacheEntry->UserData,
ReadLength,
&BytesRead) != ESUCCESS)
{
BlDiskCacheFreeRangeEntry(pNewCacheEntry);
goto SkipCache;
}

//
// Define the firmware entry point numbers.
//

typedef enum _FIRMWARE_ENTRY {
LoadRoutine,
InvokeRoutine,
ExecuteRoutine,
HaltRoutine,
PowerDownRoutine,
RestartRoutine,
RebootRoutine,
InteractiveModeRoutine,
Reserved1,
GetPeerRoutine,
GetChildRoutine,
GetParentRoutine,
GetDataRoutine,
AddChildRoutine,
DeleteComponentRoutine,
GetComponentRoutine,
SaveConfigurationRoutine,
GetSystemIdRoutine,
MemoryRoutine,
Reserved2,
GetTimeRoutine,
GetRelativeTimeRoutine,
GetDirectoryEntryRoutine,
OpenRoutine,
CloseRoutine,
ReadRoutine,
ReadStatusRoutine,
WriteRoutine,
SeekRoutine,
MountRoutine,
GetEnvironmentRoutine,
SetEnvironmentRoutine,
GetFileInformationRoutine,
SetFileInformationRoutine,
FlushAllCachesRoutine,
TestUnicodeCharacterRoutine,
GetDisplayStatusRoutine,
MaximumRoutine
} FIRMWARE_ENTRY;


//
// Define I/O functions.
//

#define ArcClose              FIRMWARE_VECTOR_BLOCK->CloseRoutine
#define ArcGetReadStatus      FIRMWARE_VECTOR_BLOCK->ReadStatusRoutine
#define Arc

http://www.dtcms.com/a/307605.html

相关文章:

  • 解决 IntelliJ IDEA Build时 Lombok 不生效问题
  • 商旅平台怎么选?如何规避商旅流程中的违规风险?
  • 【未解决】STM32无刷电机驱动电路问题记录
  • .NET Core部署服务器
  • 智慧收银系统开发进销存库存统计,便利店、水果店、建材与家居行业的库存汇总管理—仙盟创梦IDE
  • Spring Boot 异常处理:从全局捕获到优化用户体验!
  • PostgreSQL面试题及详细答案120道(01-20)
  • 解放双手!Report Distro 实现报表自动化分发
  • 微软发布Microsoft Sentinel数据湖国际版
  • SecurityContextHolder 管理安全上下文的核心组件详解
  • 【STM32】HAL库中的实现(一)GPIO/SysTick/EXTI
  • 【运维基础】Linux 计划任务管理
  • AI 安监系统:为工业园安全保驾护航
  • 社会治安满意度调查:为城市安全治理提供精准参考(满意度调查公司)
  • LeetCode 85:最大矩形
  • 光伏热斑误检率↓79%!陌讯多模态融合算法在智慧能源的落地优化
  • 融合数字孪生的智慧能源光伏场站检测系统应用解析
  • MongoDB用户认证authSource
  • Unity_数据持久化_PlayerPrefs存储各数据类型
  • Unity UI的未来之路:从UGUI到UI Toolkit的架构演进与特性剖析(6)
  • 【笔记】重学单片机(51)
  • Mac上优雅简单地使用Git:从入门到高效工作流
  • threejs创建自定义多段柱
  • 浅谈物联网嵌入式程序开发源码技术方案
  • STORM代码阅读笔记
  • 邢台市某区人民医院智慧康养平台建设项目案例研究
  • Mac安装Navicat教程Navicat Premium for Mac v17.1.9 Mac安装navicat【亲测】
  • 【ARM】PK51关于内存模式的解析与选择
  • c++:设计模式训练
  • 两款免费数据恢复软件介绍,Win/Mac均可用