標籤

2008年10月14日 星期二

CPU ID

/*
用CPUID指令,首先你可以確定你用的CPU是Intel的。
然後執行:
MOV EAX,01H
CPUID
如果返回的EDX中,低18位為1,那麼這個CPU就是支持序列號的。
此時EAX就是序列號的高32位。這32位對同一型號的CPU是一樣的。
再執行:
MOV EAX,03H
CPUID
此時的EDX:ECX就是序列號的第64位。

要想關閉這個ID,可執行下列代碼:

MOV ECX,119H
RDMSR
OR EAX,00200000H
WRMSR

不過,一旦執行上述代碼,cpu將一直不能取id,直到下次reset。

*/

#include
#include

int main(int argc,char **argv)
{
unsigned long s1,s2;
unsigned char vendor_id[]="------------";
char sel;
printf("Select the function:\n1-------Read CPU id.\n2-------Disable CPU id.\n");
sel=getch();
switch(sel)
{
case '1':
asm xor eax,eax
asm cpuid
asm mov dword ptr vendor_id,ebx
asm mov dword ptr vendor_id[+4],edx
asm mov dword ptr vendor_id[+8],ecx
printf("%s-",vendor_id);
asm mov eax,01h
asm xor edx,edx
asm cpuid
asm mov s1,edx
asm mov s2,eax
printf("%08X\n%08X-",s1,s2);
asm mov eax,03h
asm xor ecx,ecx
asm xor edx,edx
asm cpuid
asm mov s1,edx
asm mov s2,ecx
printf("%08X-%08X\n",s1,s2);
break;
case '2':
asm{
mov ecx,119h
rdmsr
or eax,00200000h
wrmsr
}
printf("CPU id is disabled.\n");
break;
}
return 0;
}

最後說一下,那個關閉CPUID的功能我一直也不能成功,總是非法指令。

-*-*-PATCH-*-*-2002-07-19

找到一個比較正規底程式


#include
#include
#include

//#define cpuid asm emit 0fh asm emit 0a2h

int GetProcessorSerialNumber(unsigned int processor, unsigned int &psn_00_31, unsigned int &psn_32_63, unsigned int &psn_64_95)
{
unsigned int wCPU_SerialNo_00_31 = 0;
unsigned int wCPU_SerialNo_32_63 = 0;
unsigned int wCPU_SerialNo_64_93 = 0;

#define PSN_FLAG 0x4000//(0x1 << 18) int errCode = 0; int psn_falg = PSN_FLAG; DWORD_PTR dwThreadAffinitiMask = (0x1 << processor); SetThreadAffinityMask(GetCurrentThread(), dwThreadAffinitiMask); asm { pushad mov eax,0 cpuid cmp ebx, 'uneG' jne wrong_processor cmp edx, 'Ieni' jne wrong_processor cmp ecx, 'letn' jne wrong_processor // now we have an Intel-Processor: // get CPU feature flag.. mov eax,1 cpuid mov wCPU_SerialNo_64_93,eax and edx,PSN_FLAG cmp edx,PSN_FLAG jne psn_not_supported_or_disabled // get the PSN mov eax,3 cpuid mov wCPU_SerialNo_32_63,edx mov wCPU_SerialNo_00_31,ecx jmp _exit_ wrong_processor: mov errCode, 1; jmp _exit_; psn_not_supported_or_disabled: mov errCode, 2; _exit_: popad } psn_00_31 = wCPU_SerialNo_00_31; psn_32_63 = wCPU_SerialNo_32_63; psn_64_95 = wCPU_SerialNo_64_93; dwThreadAffinitiMask = 0xffffffff; SetThreadAffinityMask(GetCurrentThread(), dwThreadAffinitiMask); return errCode; } int _tmain(int argc, _TCHAR* argv[]) { unsigned int psn_00_31; unsigned int psn_32_63; unsigned int psn_64_95; int iRet = GetProcessorSerialNumber(0, psn_00_31, psn_32_63, psn_64_95); _tprintf(_T("\nPSN (%d): %.8x-%.8x-%.8x\n"), iRet, psn_64_95, psn_32_63, psn_00_31); return 0; } For more info see: Intel?Processor Identification and the CPUID Instruction http://www.intel.com/design/xeon/applnots/241618.htm -*-*-PATCH-*-*-2002-07-19   誰有讀取CPU序列號或其他硬體唯一標識碼的源碼,100大洋相送! -------------------------------------------------------------------------------- amiao_107@163.com amiao107@sohu.com -------------------------------------------------------------------------------- procedure GetCpuInfo; var R: array[0..19] of Char; var CpuID: Integer; begin FillChar(R, 20, 0); asm mov eax, 0 db 0fh, 0a2h // 其實就是cpuid匯編指令
mov dword ptr R[0], ebx
mov dword ptr R[4], edx
mov dword ptr R[8], ecx
mov eax, 1
db 0fh, 0a2h // cpuid
mov CpuID, edx
end;
ShowMessage('CPU製造商為:' + R);
ShowMessage('序列號為:' + IntToStr(CpuID));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
GetCpuInfo;
end;

--------------------------------------------------------------------------------

GZ

--------------------------------------------------------------------------------

procedure GetCpuInfo;
var R: array[0..19] of Char;
var CpuID: Integer;
begin
FillChar(R, 20, 0);
asm
mov eax, 0
db 0fh, 0a2h // 其實就是cpuid匯編指令
mov dword ptr R[0], ebx
mov dword ptr R[4], edx
mov dword ptr R[8], ecx
mov eax, 1
db 0fh, 0a2h // cpuid
mov CpuID, edx
end;
ShowMessage('CPU製造商為:' + R);
ShowMessage('序列號為:' + IntToStr(CpuID));
end;

--------------------------------------------------------------------------------

呵呵,我試試

--------------------------------------------------------------------------------

gz

--------------------------------------------------------------------------------

不行啊,在多台機上測試得出的CPU的ID相同,都為25426431,這是為什麼?

--------------------------------------------------------------------------------

學習!
試了一下:
CPU製造商為:GenuineIntel
序列號為:8452697

--------------------------------------------------------------------------------

怎麼解決這樣的問題,在多台機器上得到的CPU的ID都相同,為什麼會有這樣的情況出現

--------------------------------------------------------------------------------

函數原形如下:
VOID GetSystemInfo(LPSYSTEM_INFO lpSystemInfo);//指向系統信息結構的指針;
例:
var :System_Info;
begin
GetSystemInfo(SysInfo);//獲取CPU信息;
Edit1.text:='系統共有'+IntTostr(SysInfo.dwNumberOfProcessors)+'個CPU';
end;

--------------------------------------------------------------------------------

學習。
試了一下luoweicaisd(笑三少) 的代碼:
CPU製造商為:GenuineIntel
序列號為:8452607
正確!

--------------------------------------------------------------------------------

支持2000嗎?

--------------------------------------------------------------------------------

OK,好使

--------------------------------------------------------------------------------

用上面那段代碼,為什麼多台機器測出的CPU ID 都是一樣的?兩台塞羊(型號一樣)和一台奔三,得到的結果都是一樣的。

--------------------------------------------------------------------------------

下面是得到BIOS資訊的代碼:procedure TForm1.BiosInfo;
const
Subkey: string = ''Hardware\description\system'';
var
hkSB: HKEY;
rType: LongInt;
ValueSize, OrigSize: Longint;
ValueBuf: array[0..1000] of char;
procedure ParseValueBuf(const VersionType: string);
var
I, Line: Cardinal;
S: string;
begin
i := 0;
Line := 0;
while ValueBuf[i] <> #0 do
begin
S := StrPas(@ValueBuf[i]); // move the Pchar into a string
Inc(Line);
Memo1.Lines.Append(Format(''%s Line %d = %s'',
[VersionType, Line, S])); // add it to a Memo
inc(i, Length(S) + 1);
// to point to next sz, or to #0 if at
end
end;
end;
begin
if RegOpenKeyEx(HKEY_LOCAL_MACHINE, PChar(Subkey), 0,
KEY_READ, hkSB) = ERROR_SUCCESS then
try
OrigSize := sizeof(ValueBuf);
ValueSize := OrigSize;
rType := REG_MULTI_SZ;
if RegQueryValueEx(hkSB, ''SystemBiosVersion'', nil, @rType,
@ValueBuf, @ValueSize) = ERROR_SUCCESS then
ParseValueBuf(''System BIOS Version'');
ValueSize := OrigSize;
rType := REG_SZ;
if RegQueryValueEx(hkSB, ''SystemBIOSDate'', nil, @rType,
@ValueBuf, @ValueSize) = ERROR_SUCCESS then
Memo1.Lines.Append(''System BIOS Date '' + ValueBuf);
ValueSize := OrigSize;
rType := REG_MULTI_SZ;
if RegQueryValueEx(hkSB, ''VideoBiosVersion'', nil, @rType,
@ValueBuf, @ValueSize) = ERROR_SUCCESS then
ParseValueBuf(''Video BIOS Version'');
ValueSize := OrigSize;
rType := REG_SZ;
if RegQueryValueEx(hkSB, ''VideoBIOSDate'', nil, @rType,
@ValueBuf, @ValueSize) = ERROR_SUCCESS then
Memo1.Lines.Append(''Video BIOS Date '' + ValueBuf);
finally
RegCloseKey(hkSB);
end;
end;


--------------------------------------------------------------------------------

讀硬盤的序列號
procedure GetHardDriveinfo;
var
VolumeSerialNumber : DWORD;
MaximumComponentLength : DWORD;
FileSystemFlags : DWORD;
SerialNumber : string;
drv:string;
begin
drv:='c:\';//可以改成其他硬盤
GetVolumeInformation(pchar(drv) , nil,0,@VolumeSerialNumber,
MaximumComponentLength, FileSystemFlags,
nil,0);
SerialNumber:=inttostr(VolumeSerialNumber);
ShowMessage(SerialNumber);
end;

--------------------------------------------------------------------------------

許多FAQ中推薦使用GetVolumeInformation來獲取硬盤序列號。但是那獲取的是卷的序列號,而不是硬盤的序列號。卷的序列號是在分區格式化時生成或修改。一些公司使用復制工具來為全部新計算機安裝軟件----通過將一個硬盤復制到其他硬盤,當然,所有這些計算機上卷的資訊(包括序列號)都是相同的。

我建議另外的一個方法:獲取真正硬盤的序列號。
不幸的是,下列代碼只能工作在IDE硬盤上。


//獲取第一個IDE硬盤的序列號
function GetIdeSerialNumber : SerialNumber;
const IDENTIFY_BUFFER_SIZE = 512;
type
TIDERegs = packed record
bFeaturesReg : BYTE; // Used for specifying SMART "commands".
bSectorCountReg : BYTE; // IDE sector count register
bSectorNumberReg : BYTE; // IDE sector number register
bCylLowReg : BYTE; // IDE low order cylinder value
bCylHighReg : BYTE; // IDE high order cylinder value
bDriveHeadReg : BYTE; // IDE drive/head register
bCommandReg : BYTE; // Actual IDE command.
bReserved : BYTE; // reserved for future use. Must be zero.
end;
TSendCmdInParams = packed record
// Buffer size in bytes
cBufferSize : DWORD;
// Structure with drive register values.
irDriveRegs : TIDERegs;
// Physical drive number to send command to (0,1,2,3).
bDriveNumber : BYTE;
bReserved : Array[0..2] of Byte;
dwReserved : Array[0..3] of DWORD;
bBuffer : Array[0..0] of Byte; // Input buffer.
end;
TIdSector = packed record
wGenConfig : Word;
wNumCyls : Word;
wReserved : Word;
wNumHeads : Word;
wBytesPerTrack : Word;
wBytesPerSector : Word;
wSectorsPerTrack : Word;
wVendorUnique : Array[0..2] of Word;
sSerialNumber : Array[0..19] of CHAR;
wBufferType : Word;
wBufferSize : Word;
wECCSize : Word;
sFirmwareRev : Array[0..7] of Char;
sModelNumber : Array[0..39] of Char;
wMoreVendorUnique : Word;
wDoubleWordIO : Word;
wCapabilities : Word;
wReserved1 : Word;
wPIOTiming : Word;
wDMATiming : Word;
wBS : Word;
wNumCurrentCyls : Word;
wNumCurrentHeads : Word;
wNumCurrentSectorsPerTrack : Word;
ulCurrentSectorCapacity : DWORD;
wMultSectorStuff : Word;
ulTotalAddressableSectors : DWORD;
wSingleWordDMA : Word;
wMultiWordDMA : Word;
bReserved : Array[0..127] of BYTE;
end;
PIdSector = ^TIdSector;
TDriverStatus = packed record
// 驅動器返回的錯誤代碼,無錯則返回0
bDriverError : Byte;
// IDE出錯寄存器的內容,只有當bDriverError 為 SMART_IDE_ERROR 時有效
bIDEStatus : Byte;
bReserved : Array[0..1] of Byte;
dwReserved : Array[0..1] of DWORD;
end;
TSendCmdOutParams = packed record
// bBuffer的大小
cBufferSize : DWORD;
// 驅動器狀態
DriverStatus : TDriverStatus;
// 用於保存從驅動器讀出的數據的緩沖區,實際長度由cBufferSize決定
bBuffer : Array[0..0] of BYTE;
end;

var hDevice : THandle;
cbBytesReturned : DWORD;
ptr : PChar;
SCIP : TSendCmdInParams;
aIdOutCmd : Array [0..(SizeOf(TSendCmdOutParams)+IDENTIFY_BUFFER_SIZE-1)-1] of Byte;
IdOutCmd : TSendCmdOutParams absolute aIdOutCmd;

procedure ChangeByteOrder( var Data; Size : Integer );
var ptr : PChar;
i : Integer;
c : Char;
begin
ptr := @Data;
for i := 0 to (Size shr 1)-1 do
begin
c := ptr^;
ptr^ := (ptr+1)^;
(ptr+1)^ := c;
Inc(ptr,2);
end;
end;

begin
Result := ''; // 如果出錯則返回空串
if SysUtils.Win32Platform=VER_PLATFORM_WIN32_NT then // Windows NT, Windows 2000
begin
// 提示! 改變名稱可適用於其他驅動器,如第二個驅動器: '\\.\PhysicalDrive1\'
hDevice := CreateFile( '\\.\PhysicalDrive0', GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0 );
end
else // Version Windows 95 OSR2, Windows 98
hDevice := CreateFile( '\\.\SMARTVSD', 0, 0, nil, CREATE_NEW, 0, 0 );
if hDevice=INVALID_HANDLE_VALUE then Exit;
try
FillChar(SCIP,SizeOf(TSendCmdInParams)-1,#0);
FillChar(aIdOutCmd,SizeOf(aIdOutCmd),#0);
cbBytesReturned := 0;
// Set up data structures for IDENTIFY command.
with SCIP do
begin
cBufferSize := IDENTIFY_BUFFER_SIZE;
// bDriveNumber := 0;
with irDriveRegs do
begin
bSectorCountReg := 1;
bSectorNumberReg := 1;
// if Win32Platform=VER_PLATFORM_WIN32_NT then bDriveHeadReg := $A0
// else bDriveHeadReg := $A0 or ((bDriveNum and 1) shl 4);
bDriveHeadReg := $A0;
bCommandReg := $EC;
end;
end;
if not DeviceIoControl( hDevice, $0007c088, @SCIP, SizeOf(TSendCmdInParams)-1,
@aIdOutCmd, SizeOf(aIdOutCmd), cbBytesReturned, nil ) then Exit;
finally
CloseHandle(hDevice);
end;
with PIdSector(@IdOutCmd.bBuffer)^ do
begin
ChangeByteOrder( sSerialNumber, SizeOf(sSerialNumber) );
(PChar(@sSerialNumber)+SizeOf(sSerialNumber))^ := #0;
Result := PChar(@sSerialNumber);
end;
end;

// 更多關於 S.M.A.R.T. ioctl 的信息可查看:
// http://www.microsoft.com/hwdev/download/respec/iocltapi.rtf

// MSDN庫中也有一些簡單的例子
// Windows Development -> Win32 Device Driver Kit ->
// SAMPLE: SmartApp.exe Accesses SMART stats in IDE drives

// 還可以查看 http://www.mtgroup.ru/~alexk
// IdeInfo.zip - 一個簡單的使用了S.M.A.R.T. Ioctl API的Delphi應用程式

// 注意:

// WinNT/Win2000 - 你必須擁有對硬盤的讀/寫訪問權限

// Win98
// SMARTVSD.VXD 必須安裝到 \windows\system\iosubsys
// (不要忘記在復制後重新啟動系統)

--------------------------------------------------------------------------------

在我的前一篇文章(http://www.ocloud.com/article/viewart.php?id=88)裏我描述了一個通過調用以DFP_RECEIVE_DRIVE_DATA為控制碼的DeviceIoControl函數來獲取序列號,但在NT上只有擁有系統管理員權限的用戶才能使用。現在我找到了讓所有用戶都可以使用的方法。
{ 構件下載:IdeSN.zip }
提示! 在Win9x系統中必須安裝smartvsd.vxd:你只需簡單的將它從\windows\system\目錄中復制到\windows\system\iosubsys\下,然後重新啟動系統。

// (c) Alex Konshin 30 jul 2000

program IdeSN;

// 目的:簡單的控制臺程式以獲取第一個IDE硬盤的序列號

{$APPTYPE CONSOLE}

uses
Windows,
SysUtils; // only for Win32Platform and SysErrorMessage

//-------------------------------------------------------------
function GetIdeDiskSerialNumber : String;
type
TSrbIoControl = packed record
HeaderLength : ULONG;
Signature : Array[0..7] of Char;
Timeout : ULONG;
ControlCode : ULONG;
ReturnCode : ULONG;
Length : ULONG;
end;
SRB_IO_CONTROL = TSrbIoControl;
PSrbIoControl = ^TSrbIoControl;

TIDERegs = packed record
bFeaturesReg : Byte; // Used for specifying SMART "commands".
bSectorCountReg : Byte; // IDE sector count register
bSectorNumberReg : Byte; // IDE sector number register
bCylLowReg : Byte; // IDE low order cylinder value
bCylHighReg : Byte; // IDE high order cylinder value
bDriveHeadReg : Byte; // IDE drive/head register
bCommandReg : Byte; // Actual IDE command.
bReserved : Byte; // reserved. Must be zero.
end;
IDEREGS = TIDERegs;
PIDERegs = ^TIDERegs;

TSendCmdInParams = packed record
cBufferSize : DWORD;
irDriveRegs : TIDERegs;
bDriveNumber : Byte;
bReserved : Array[0..2] of Byte;
dwReserved : Array[0..3] of DWORD;
bBuffer : Array[0..0] of Byte;
end;
SENDCMDINPARAMS = TSendCmdInParams;
PSendCmdInParams = ^TSendCmdInParams;

TIdSector = packed record
wGenConfig : Word;
wNumCyls : Word;
wReserved : Word;
wNumHeads : Word;
wBytesPerTrack : Word;
wBytesPerSector : Word;
wSectorsPerTrack : Word;
wVendorUnique : Array[0..2] of Word;
sSerialNumber : Array[0..19] of Char;
wBufferType : Word;
wBufferSize : Word;
wECCSize : Word;
sFirmwareRev : Array[0..7] of Char;
sModelNumber : Array[0..39] of Char;
wMoreVendorUnique : Word;
wDoubleWordIO : Word;
wCapabilities : Word;
wReserved1 : Word;
wPIOTiming : Word;
wDMATiming : Word;
wBS : Word;
wNumCurrentCyls : Word;
wNumCurrentHeads : Word;
wNumCurrentSectorsPerTrack : Word;
ulCurrentSectorCapacity : ULONG;
wMultSectorStuff : Word;
ulTotalAddressableSectors : ULONG;
wSingleWordDMA : Word;
wMultiWordDMA : Word;
bReserved : Array[0..127] of Byte;
end;
PIdSector = ^TIdSector;

const
IDE_ID_FUNCTION = $EC;
IDENTIFY_BUFFER_SIZE = 512;
DFP_RECEIVE_DRIVE_DATA = $0007c088;
IOCTL_SCSI_MINIPORT = $0004d008;
IOCTL_SCSI_MINIPORT_IDENTIFY = $001b0501;
DataSize = sizeof(TSendCmdInParams)-1+IDENTIFY_BUFFER_SIZE;
BufferSize = SizeOf(SRB_IO_CONTROL)+DataSize;
W9xBufferSize = IDENTIFY_BUFFER_SIZE+16;
var
hDevice : THandle;
cbBytesReturned : DWORD;
pInData : PSendCmdInParams;
pOutData : Pointer; // PSendCmdOutParams
Buffer : Array[0..BufferSize-1] of Byte;
srbControl : TSrbIoControl absolute Buffer;

procedure ChangeByteOrder( var Data; Size : Integer );
var ptr : PChar;
i : Integer;
c : Char;
begin
ptr := @Data;
for i := 0 to (Size shr 1)-1 do
begin
c := ptr^;
ptr^ := (ptr+1)^;
(ptr+1)^ := c;
Inc(ptr,2);
end;
end;

begin
Result := '';
FillChar(Buffer,BufferSize,#0);
if Win32Platform=VER_PLATFORM_WIN32_NT then
begin // Windows NT, Windows 2000
// 獲取 SCSI port handle
hDevice := CreateFile( '\\.\Scsi0:',
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE,
nil, OPEN_EXISTING, 0, 0 );
if hDevice=INVALID_HANDLE_VALUE then Exit;
try
srbControl.HeaderLength := SizeOf(SRB_IO_CONTROL);
System.Move('SCSIDISK',srbControl.Signature,8);
srbControl.Timeout := 2;
srbControl.Length := DataSize;
srbControl.ControlCode := IOCTL_SCSI_MINIPORT_IDENTIFY;
pInData := PSendCmdInParams(PChar(@Buffer)
+SizeOf(SRB_IO_CONTROL));
pOutData := pInData;
with pInData^ do
begin
cBufferSize := IDENTIFY_BUFFER_SIZE;
bDriveNumber := 0;
with irDriveRegs do
begin
bFeaturesReg := 0;
bSectorCountReg := 1;
bSectorNumberReg := 1;
bCylLowReg := 0;
bCylHighReg := 0;
bDriveHeadReg := $A0;
bCommandReg := IDE_ID_FUNCTION;
end;
end;
if not DeviceIoControl( hDevice, IOCTL_SCSI_MINIPORT,
@Buffer, BufferSize, @Buffer, BufferSize,
cbBytesReturned, nil ) then Exit;
finally
CloseHandle(hDevice);
end;
end
else
begin // Windows 95 OSR2, Windows 98
hDevice := CreateFile( '\\.\SMARTVSD', 0, 0, nil,
CREATE_NEW, 0, 0 );
if hDevice=INVALID_HANDLE_VALUE then Exit;
try
pInData := PSendCmdInParams(@Buffer);
pOutData := @pInData^.bBuffer;
with pInData^ do
begin
cBufferSize := IDENTIFY_BUFFER_SIZE;
bDriveNumber := 0;
with irDriveRegs do
begin
bFeaturesReg := 0;
bSectorCountReg := 1;
bSectorNumberReg := 1;
bCylLowReg := 0;
bCylHighReg := 0;
bDriveHeadReg := $A0;
bCommandReg := IDE_ID_FUNCTION;
end;
end;
if not DeviceIoControl( hDevice, DFP_RECEIVE_DRIVE_DATA,
pInData, SizeOf(TSendCmdInParams)-1, pOutData,
W9xBufferSize, cbBytesReturned, nil ) then Exit;
finally
CloseHandle(hDevice);
end;
end;
with PIdSector(PChar(pOutData)+16)^ do
begin
ChangeByteOrder(sSerialNumber,SizeOf(sSerialNumber));
SetString(Result,sSerialNumber,SizeOf(sSerialNumber));
end;
end;


//=============================================================
var s : String;
rc : DWORD;
begin
s := GetIdeDiskSerialNumber;
if s='' then
begin
rc := GetLastError;
if rc=0 then WriteLn('IDE drive is not support SMART feature')
else WriteLn(SysErrorMessage(rc));
end
else WriteLn('Disk serial number: ''', s,'''');
end.

--------------------------------------------------------------------------------

以上文章均轉載於淩雲天地,未加編輯,不是本人所作

--------------------------------------------------------------------------------

感謝fontain等網友的關注,為表感謝,送分了,接分了...

--------------------------------------------------------------------------------

你讀取硬體唯一標識碼的目的是為了控制你的軟件只在一台機器上運行,是嗎?那我建議你讀取網卡的物理位址,可行性有:1、眾所周知:網卡的物理位址獨一無二的;2、網卡的價格很低;怎麼樣?

沒有留言: