備忘録

備忘録

.protoをシリアライズされているバイナリデータから作る

Ⅰ. はじめに

私の知る限りバイナリから自動で.protoを生成するツールは公開されていません。
よって、シリアライズされているバイナリデータを読み取る為の.proto作成は手動となります。

また、ProtocolBuffersはシリアライズした時key-valueのkey情報が失われる為非常に手間がかかります。
インデックス番号のみ含まれています。
key情報があれば値が何の値を表しているかの情報として利用できる為分かりやすいですが、
key情報が無い為データ値を見て手探りする必要があります。

本記事では少しでも楽に.protoファイルを作る方法をまとめます。

Protocol Buffers の原理についてはこちら
https://developers.google.com/protocol-buffers/docs/encoding

また、この記事でサンプルとして使う元データは以下の通りです。

armor.proto (proto3)

message Armor {
  uint64 id = 1;
  string name = 2;
  repeated Option attribute = 3;
}

message Option {
  uint32 id = 1;
  string detail = 2;
}

データ(C#)

var armors = new List<Armor>();

var fireOption = new Option() { Id = 1001, Detail = "火属性 耐性" };
var waterOption = new Option() { Id = 1002, Detail = "水属性 耐性" };
var woodOption = new Option() { Id = 1003, Detail = "木属性 耐性" };

armors.Add(new Armor() { Id = 100001, Name = "防具001", Attribute = new Option[] { fireOption } });
armors.Add(new Armor() { Id = 100002, Name = "防具002", Attribute = new Option[] { waterOption } });
armors.Add(new Armor() { Id = 100003, Name = "防具003", Attribute = new Option[] { waterOption, woodOption } });

Ⅱ. protofudger を使う方法

GO言語で書かれた非常に優秀なツールです。

ただし、文字列が 1024 * 32バイト(32,768バイト)を超えると例外を吐くので、
fork して修正したものも置いておきます。

使い方
protofudger.exe binary_file > out.txt
出力結果
1: {
  1: (varint) 100001
  2: (string) "防具001"
  3: {
    1: (varint) 1001
    2: (string) "火属性 耐性"
  }
}
1: {
  1: (varint) 100002
  2: (string) "防具002"
  3: {
    1: (varint) 1002
    2: (string) "水属性 耐性"
  }
}
1: {
  1: (varint) 100003
  2: (string) "防具003"
  3: {
    1: (varint) 1002
    2: (string) "水属性 耐性"
  }
  3: {
    1: (varint) 1003
    2: (string) "木属性 耐性"
  }
}

Ⅲ. Protobuf Viewerを使う方法


macOSでのみ動作します。(2017/03/17時点)
GUIがあるのでmacOSが使える状況であればコレが一番使いやすいです。
また、データが欠損している不完全なバイナリファイルでも解読が可能です。
(protoc --decode_rawしても「Filed to parse input.」と出るようなデータでも解析が可能)

https://sourceforge.net/projects/protobufviewer/

Ⅳ. protoc を使う方法

こちらからダウンロードできます。
https://github.com/google/protobuf/releases

また、NuGetから Google.ProtocolBuffers パッケージをインストールすると protoc.exe も含まれています。

使い方
protoc.exe --decode_raw < binary_file > out.txt
作った.protoをテストする場合
protoc --decode=PackageName.MessageName MessageName.proto < binary_file > out.txt
出力結果
1 {
  1: 100001
  2: "\351\230\262\345\205\267001"
  3 {
    1: 1001
    2: "\347\201\253\345\261\236\346\200\247 \350\200\220\346\200\247"
  }
}
1 {
  1: 100002
  2: "\351\230\262\345\205\267002"
  3 {
    1: 1002
    2: "\346\260\264\345\261\236\346\200\247 \350\200\220\346\200\247"
  }
}
1 {
  1: 100003
  2: "\351\230\262\345\205\267003"
  3 {
    1: 1002
    2: "\346\260\264\345\261\236\346\200\247 \350\200\220\346\200\247"
  }
  3 {
    1: 1003
    2: "\346\234\250\345\261\236\346\200\247 \350\200\220\346\200\247"
  }
}

Ⅴ. protobuf-inspectorを使う

説明省略
https://github.com/mildsunrise/protobuf-inspector

Ⅵ. d3を使う

Diablo 3というゲームの.protoを自動生成する為に作られたプロジェクトのようです。
私の環境でDiablo 3ではない物を対象にして.protoの自動生成を試みましたが上手くいきませんでした。
https://github.com/fry/d3

Androidで複数キーでソートする

Ⅰ. はじめに

この記事ではStream APIを利用したソートとLightweight-Stream-APIを利用したソートの2つを紹介します。
Java8で追加されたStream APIとComparatorを使ったソートはAndroid N (Android 7.0)以上でなければ動作しません。
Android N 以下の場合はlambdaで例外が発生しアプリが強制終了します。

Ⅱ. 使うライブラリ

Lightweight-Stream-API を使います。
Android 4.4.4で動作確認済みです。

略称はLSAだそうです。(ライブラリ作者がReadmeでLSAと書いている)

LSAはStream APIではできない書き方ができるので便利です。

例えば、以下のようにメソッドの評価結果でソートが可能です。

persons = persons
       .sorted(theComparing(comparing(x -> x.getSomething(argSomething))))
       .toList();

ラムダ式を使ったこの書き方はStream APIでも1つだけのソートであれば可能ですが、
複数キーでソートする場合は不可能です。
なんとも中途半端な作りですね・・・

// OK
persons.sort(comparing(x -> x.name));
// OK
persons.sort(comparing(x -> x.getName()));
// NG
persons.sort(comparing(x -> x.name).thenComparing(x -> x.age));
// NG
persons.sort(comparing(x -> x.getName()).thenComparing(x -> x.getAge()));

また、ixJavaなど他のライブラリもありましたが、複数キーでソートするにはLSAが最適でした。
(ixJavaはそもそも複数キーソートに対応していない)

Ⅲ. プログラム

import java.util.ArrayList;
import java.util.List;

public class Main {

  static class Person {

    public Person(String name, Integer age){
      this.name = name;
      this.age = age;
    }

    private String name;
    public String getName() {
      return name;
    }

    private Integer age;
    public Integer getAge() {
      return age;
    }
  }

  public static void main(String[] args) {

    List<Person> persons = new ArrayList<>();

    persons.add(new Person("a", 1));
    persons.add(new Person("b", 4));
    persons.add(new Person("b", 2));
    persons.add(new Person("c", 3));

    // Stream API
    // Android 7.0未満では動きません
    // import static java.util.Comparator.*;
    // persons.sort(comparing(Person::getName, reverseOrder()).thenComparing(Person::getAge));

    // Lightweight-Stream-API
    // Android 4.4.4で動作確認済み
    // import static com.annimon.stream.ComparatorCompat.*;
    persons = Stream.of(persons)
           .sorted(thenComparing(comparing(x -> x.name, reverseOrder()), comparing(x -> x.age)))
           .toList();

    // または
    //persons = Stream.of(persons)
    //       .sorted(thenComparing(comparing(Person::getName, reverseOrder()), comparing(Person::getAge)))
    //       .toList();

    for(Person p : persons) {
      System.out.println(p.name + p.age);
    }

  }
}

Ⅳ. 出力結果

c3
b2
b4
a1

自作PCを作るときにおすすめなサイト

Ⅰ. はじめに

基本的に、自作PCを作るときはパーツのリストを作ってから購入に踏み切ると思います。
その時のリスト作りに役立つWEBサイトの紹介です。

pcpartpicker.com


https://pcpartpicker.com/list/

作ったリストの消費電力まで計算してくれる優秀なサイトです。

価格.com ピックアップリスト


http://kakaku.com/pickuplist/

日本人に人気のサイトです。
作ったリストに対するアドバイスが得られる場合があります。

Steamハードウェア&ソフトウェア 調査

https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam

Steamユーザの統計情報を得る事が出来ます。

JavaでJSONを扱う(GSONの使い方)

Ⅰ. はじめに

JavaJSONを扱う方法です
最近人気のGoogleが開発しているライブラリ GSON を利用します。

C#の場合はJSON.NETを利用すれば、クラスを作らなくてもJSONをdynamicに利用したり、LINQ to Objects ができますが、GSONはクラスが必須です。
※2018/03/24追記
GSONでもクラスが不要な書き方がサポートされていました。

この記事で利用するサンプルのJSON

[
  {"id": 1, "name": "name001", "age": 20},
  {"id": 2, "name": "name002", "age": 21},
  {"id": 3, "name": "name003", "age": 22}
]

Ⅱ. 使い方(クラスを作る場合)

1. JSONJavaのクラスに変換する

手動での変換が面倒な場合は以下のようなWebサイトを利用します。
http://www.jsonschema2pojo.org/
f:id:kagasu:20170310171151p:plain

2. Main.java

String strJson = "[{\"id\": 1, \"name\": \"name001\", \"age\": 20},{\"id\": 2, \"name\": \"name002\", \"age\": 21},{\"id\": 3, \"name\": \"name003\", \"age\": 22}]";

Gson gson = new Gson();
User[] users = gson.fromJson(strJson, User[].class);

for(User user : users) {
	System.out.println(user.name);
}

3. 実行結果

name001
name002
name003

Ⅲ. 使い方(クラスを作らない場合)

1. Main.java
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;

public class Main {
    public static void main(String[] args){
        String strJson = "[{\"id\": 1, \"name\": \"name001\", \"age\": 20},{\"id\": 2, \"name\": \"name002\", \"age\": 21},{\"id\": 3, \"name\": \"name003\", \"age\": 22}]";

        JsonParser parser = new JsonParser();
        JsonArray objs = parser.parse(strJson).getAsJsonArray();

        for(JsonElement obj : objs) {
            System.out.println(obj.getAsJsonObject().get("name").getAsString());
        }
    }
}

3. 実行結果

name001
name002
name003

Javaでokhttpを使って全ての証明書を許可する

Ⅰ. はじめに

オレオレ証明書などは以下のエラーを吐いて弾かれてしまいます。
デバッグ時など、とりあえず全部許可したいときのやり方です。

Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.
ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target

Ⅱ. プログラム

import okhttp3.*;

import javax.net.ssl.*;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

public class Main {

  static class MyX509TrustManager implements X509TrustManager {
    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { }
    public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { }
    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
      return new java.security.cert.X509Certificate[]{};
    }
  }

  private static String okHttpGet() throws Exception {
    // リクエストを作成する
    Request request = new Request.Builder()
        .url("https://example.com/")
        .get()
        .build();

    // OkHttpClient の Bulider を作成する
    OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();

    // proxyを設定する
    clientBuilder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8008)));
    
    // MyX509TrustManager を割り当てる
    // MyX509TrustManager は全ての証明書を許可するようにしている
    SSLContext sslContext = SSLContext.getInstance("SSL");
    sslContext.init(null, new TrustManager[] { new MyX509TrustManager() }, new java.security.SecureRandom());
    clientBuilder.sslSocketFactory(sslContext.getSocketFactory(), new MyX509TrustManager());

    OkHttpClient client = clientBuilder.build();

    // GET する
    Response response = client.newCall(request).execute();
    return response.body().string();
  }

  public static void main(String[] args) throws Exception{
    String str = okHttpGet();
    System.out.println(str);
  }
}