iOSデバイスのチップセット情報をプログラムから取得する

自分のiPhone6s or 6s+のA9チップがサムスン製かTSMC製かを調べる（バッテリーの持ちが違うらしい）のがタイムラインで流行っていて、その方法として下記記事がシェアされてました。

- [iPhone6s・6s+の「A9」が「サムスン版」か「TSMC版」か安全に調べる方法](http://touchlab.jp/2015/10/iphone6s_apple_a9_check/)

シリアル番号とかから判断するのかなと思いつつ記事を読んでみると、AppStoreに出ているアプリを使って判定するとのこと。なるほど、ストアに出てるということは**合法的に（Private API を使うことなく）プログラムからチップ情報を取得可能** ということなので、どうやってるんだろうと思いつつ、同日に見つけた GitHub の Trending で見かけた「[CPU-Identifier](https://github.com/hirakujira/CPU-Identifier)」というOSSを思い出しました。

中身を見てみると、次のような実装になってました。


##「[CPU-Identifier](https://github.com/hirakujira/CPU-Identifier)」の実装内容


###dlfcn.h をインクルード

```objc
#include <dlfcn.h>
```

（CPU-Identifier では sysctl.h, resource.h, vm.h もインクルードしているが、本記事でピックアップする範囲では不要）

###libMobileGestalt.dylib をロード

まず、`dlopen()` （ダイナミックライブラリをロードしてそのハンドラを返す関数）を使って `/usr/lib/` の `libMobileGestalt.dylib` をロードします。

```objc
void *gestalt = dlopen("/usr/lib/libMobileGestalt.dylib", RTLD_GLOBAL | RTLD_LAZY);
```

`libMobileGestalt.dylib` は、**UDID、ディスク使用量、デバイスバージョン等のシステム情報を取得するのに使われる**ライブラリ、とのことです。

> libMobileGestalt is a library that can be used to get various system values such as the UDID, disk usage, device version and much more. 

- http://iphonedevwiki.net/index.php/LibMobileGestalt.dylib


###"MGCopyAnswer"がロードされたメモリアドレスを取得

次に、`dlsym()` を使って、"MGCopyAnswer" というシンボルがロードされたメモリアドレスを取得します。

```objc
$MGCopyAnswer = dlsym(gestalt, "MGCopyAnswer");
```

###ボードIDを取得

上の手順で取得したメモリアドレスを格納した `MGCopyAnswer` は、MobileGestalt.h にて次のように定義されています。

```objc
CFPropertyListRef MGCopyAnswer(CFStringRef property);
```

で、この [CFPropertyListRef](https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFPropertyListRef/) から、"HardwarePlatform" 情報を次のように取り出します。

```objc
CFStringRef boardID = (CFStringRef)$MGCopyAnswer(CFSTR("HardwarePlatform"));
```

CFStringRef は Toll-Free Bridged なので、`(__bridge NSString *)boardID` とするだけで NSString にキャストできます。

- [Toll-Free Bridged Types](https://developer.apple.com/library/ios/documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html#//apple_ref/doc/uid/TP40010677-SW1)


この「ボードID」が 6s / 6s Plus だと "s8000" か "s8003" になるので、それぞれ Samsung 製、TSMC 製と判定できます。

```objc
if ([(__bridge NSString *)boardID isEqualToString:@"s8000"]) {
    manufactory.text = @"Samsung";
    isA9 = YES;
    imageName = @"A9";
}
if ([(__bridge NSString *)boardID isEqualToString:@"s8003"]) {
    manufactory.text = @"TSMC";
    isA9 = YES;
    imageName = @"A9";
}
```

- https://www.theiphonewiki.com/wiki/S8000
- https://www.theiphonewiki.com/wiki/S8003


ちなみに僕の iPhone 6s は見事に「ハズレ」とされる Samsung 製でした。。

![a9.jpg](https://qiita-image-store.s3.amazonaws.com/0/3180/d4121bd5-da2c-9deb-eee8-61230c907e28.jpeg)


###A9以外のチップの判定

さらに「CPU-Identifier」では下記コードのようにA9チップ以外も判定できるように実装されています。

```objc
NSString* str2Cmp = [(__bridge NSString *)boardID lowercaseString];
if ([str2Cmp hasPrefix:@"s5l8960"] || [str2Cmp hasPrefix:@"s5l8965"]){
    imageName = @"A7";
}else if ([str2Cmp hasPrefix:@"t7000"]){
    imageName = @"A8";
}else if ([str2Cmp hasPrefix:@"t7001"]){
    imageName = @"A8X";
}else if ([str2Cmp hasPrefix:@"s5l8950"]){
    imageName = @"A6";
}else if ([str2Cmp hasPrefix:@"S5L8955"]){
    imageName = @"A6X";
}else if ([str2Cmp hasPrefix:@"s5l8940"] || [str2Cmp hasPrefix:@"s5l8942"] ){
    imageName = @"A5";
}else if ([str2Cmp hasPrefix:@"s5l8945"]){
    imageName = @"A5X";
}else if ([str2Cmp hasPrefix:@"s5l8930"]){
    imageName = @"A4";
}
```


##別の判定方法

ここまでやってみてやっと気付いたのですが、冒頭の参考記事で紹介されているアプリとは判定方法が違うようです。記事内のアプリでは、下記のように判定を行っていました。

>
- iPhone 6s
  - N71AP：Samsung製
  - N71mAP：TSMC製
- iPhone 6s Plus
  - N66AP：Samsung製
  - N66mAP：TSMC製

ハードについて全然詳しくないので、s8000が何を指していてN71APが何を指しているかとか正直よくわからないのですが、とりあえずこの "N71AP" をどうやったら取得できるのか、`MGCopyAnswer` から色々取得して見てみました。

```objc
CFStringRef modelNumber = (CFStringRef)$MGCopyAnswer(CFSTR("ModelNumber"));
NSLog(@"modelNumber:%@", modelNumber);
    
CFStringRef chipId = (CFStringRef)$MGCopyAnswer(CFSTR("ChipID"));
NSLog(@"chipId:%@", chipId);

CFStringRef hwModel = (CFStringRef)$MGCopyAnswer(CFSTR("HWModelStr"));
NSLog(@"hwModel:%@", hwModel);
```

iPhone6sでの実行結果：

```
modelNumber:MKQT2
chipId:32768
hwModel:N71AP
```

出ました、冒頭記事（内のアプリ）は `"HWModelStr"` を用いた判定方法のようです。


ちなみにこの N71AP というのは、ググってみると Samsung 製 A9 プロセッサを使用している iPhone 6s そのもののことを指しているようです。

> This is the iPhone 6s which uses the Samsung A9 processor.

https://www.theiphonewiki.com/wiki/N71AP


##これって合法？（このコード入りで審査通る？）

冒頭で、ストアにも出せる、と書きましたが、実際のところ怪しいかもしれません。下記記事ではこの「CPU-Identifier」作者の方が配布している野良アプリを使用する方法を紹介していて、

- [自分の「iPhone 6s / 6s Plus」に搭載されている【A9】はサムスン製？TSMC製？アプリで判定！](http://tools4hack.santalab.me/iphone6s-6splus-a9-chip-judgement-manufactory-maker-samsung-tsmc-cpuidentifier.html)

ここでは、

> AppStoreで配布出来るものではないため

という記述もあります。

まぁ作者の方は単に面倒なのでこういう配布方法を取ったということは十分に考えられますが、万が一ストアに出すアプリでこの方法でハードウェア情報にアクセスしたい場合は、Appleのガイドライン等をよく読み自己責任でお願いいたします。

（たとえば[シリアル番号とかUDIDも取れてしまう](http://stackoverflow.com/questions/22734577/get-device-serial-number-on-ios-7)ので、こういうのをサーバーに送ったりするアプリはアウトかと）

```objc
CFStringRef SNumber = MGCopyAnswer(CFSTR("SerialNumber"));
NSLog (@"Serial Number : %@", SNumber);

CFStringRef UDNumber = MGCopyAnswer(CFSTR("UniqueDeviceID"));
NSLog (@"UniqueDeviceID : %@", UDNumber);
```