ファミコン


数字をカウントするだけ。プログラムの雛形にと思ってメモ。わけもわからず書いている部分が減ってきてよい感じです。

;;; 秒数をカウントして表示

        .inesprg 1              ; プログラムバンク数
        .ineschr 1              ; CHR データバンク数
        .inesmir 0              ; 水平ミラーリング
        .inesmap 0              ; マッパー

;;; =======================
;;;  割り込みハンドラバンク
;;; =======================
        .bank   1
        .org    $FFFA           ; $FFFA から開始

        .dw     main            ; VBlank 割り込み
        .dw     start           ; リセット割り込み
        .dw     0               ; ハードウェア/ソフトウェア割り込み


;;; =======================
;;;  CHR データバンク
;;; =======================
        .bank   2

        ;; sprite
        .org    $0000
        ; .incbin "sprite.chr"

        ;; BG
        .org    $1000
        ;; http://hp.vector.co.jp/authors/VA042397/nes/sample.html
        .incbin "character.chr"


;;; =======================
;;;  プログラムバンク
;;; =======================
        .bank   0

;;; 定数
FPS:    .equ    60              ; frame / second
DIGITS: .equ    6               ; 表示桁数
POS1:   .equ    $20             ; 表示位置 higher byte
POS2:   .equ    $7c             ; 表示位置 lower byte

;;; 変数
FRAME:  .equ    $00             ; frame % FPS
SECONDS:  .equ  $01             ; ここから DIGITS bytes は秒数の各桁 (十進)


        .org    $8000

;;; -----------------------
;;; 初期化

start:
        ;; PPU レジスタ初期化
        lda     #%00010000      ; 初期化中は VBlank 割り込み禁止
        sta     $2000

        ;; VBlank 待ち
.waitVBlank:
        lda     $2002
        bpl     .waitVBlank     ; VBlankが発生して $2002 の7ビット目が1になるまで待機

        ;; PPU レジスタ初期化
        lda     #%00000110      ; 初期化中はスプライトとBGを表示OFF
        sta     $2001

        ;; パレットロード
        lda     #$3f
        sta     $2006
        lda     #$00
        sta     $2006
        ldx     #0
.loadPalette:
        lda     palette, x
        sta     $2007
        inx
        cpx     #$04
        bne     .loadPalette

        ;; ゼロページクリア
        ldx     #0
        lda     #0
.clearZeropage
        sta     <SECONDS, x
        inx
        cpx     #$ff
        bne     .clearZeropage

        jsr     drawCount
        
        ;; PPU レジスタセット
        lda     #%00011110      ; スプライトとBGの表示をONにする
        sta     $2001
        lda     #%10010000      ; 割り込み許可
        sta     $2000

.loop:
        jmp     .loop

palette:
        .byte   $0f, $00, $10, $20
        

;;; -----------------------
;;; メイン

main:
        jsr     increment
        rti


;;; -----------------------
;;; サブルーチン

;;; カウンタをひとつ進める
;;; 必要ならば再描画する
increment:
        ;; FRAME をインクリメント
        inc     <FRAME
        lda     <FRAME
        cmp     #FPS
        bne     .return

        ;; 秒数を繰上げ
        lda     #0
        sta     <FRAME
        ldx     #0
.incrementSeconds
        inc     <SECONDS, x
        lda     <SECONDS, x
        cmp     #10
        bne     .draw           ; 繰上げ終わり

        lda     #0
        sta     <SECONDS, x
        inx
        cpx     #DIGITS
        bne     .incrementSeconds ; 規定桁数以下なら引き続き繰り上げ

.draw
        jsr     drawCount

.return
        rts


;;; カウンタを描画
drawCount:
        ldy     #POS2           ; 表示位置下位byte. digit ごとに decrement
        ldx     #0
.digit
        ;; 表示位置設定
        lda     #POS1
        sta     $2006
        tya
        sta     $2006
        dey
        ;; 数字描画
        lda     <SECONDS, x
        clc
        adc     #$30            ; '0'
        sta     $2007

        inx
        cpx     #DIGITS
        bne     .digit

        ;;  スクロール設定
        lda     #$00
        sta     $2005
        sta     $2005

        rts

ファミコン

カーソルを動かせるようになった。それだけなのに大変なの

;;; use nesasm
;;; カーソルを動かしてひらがなを指すだけのプログラム

        .inesprg 1            ;  プログラムに使うバンクの数
        .ineschr 1            ;  bg, sprite データに使うバンクの数
        .inesmir 0            ;  水平ミラーリング
        .inesmap 0            ;  マッパー

;;; =======================
;;;  割り込みハンドラバンク
;;; =======================

        .bank 1
        .org $FFFA

        .dw 0                 ;  VBlank割り込みハンドラ
        .dw start             ;  リセット割り込みハンドラ
        .dw 0                 ;  ハードウェア/ソフトウェア割り込みハンドラ


;;; =======================
;;;  データバンク
;;; =======================

        .bank 2

        ;; sprite
        .org $0000
        .incbin "sprite.chr"    ; カーソル画像だけ

        ;; background
        .org $1000
        .incbin "character.chr" ; http://hp.vector.co.jp/authors/VA042397/nes/sample.html


;;; =======================
;;;  プログラムバンク
;;; =======================

        .bank 0

;;; -----------------------
;;; マクロ

;;; VBlank待ちマクロ
waitVBlank:     .macro
.wait\@:
        lda     $2002           ; VBlankが発生し,$2002の7ビット目が1になるまで待機
        bpl     .wait\@
        .endm


;;; 文字描画マクロ
;;; @params char
drawc:  .macro
        lda     \1
        sta     $2007
        .endm

;;; カラーパレット読み込みマクロ
;;; @params palette
loadPalette:    .macro
        lda     #$3f
        sta     $2006
        lda     #$00
        sta     $2006

        ldx     #$00
.load\@:
        lda     \1, x
        sta     $2007        
        inx
        cpx     #$20
        bne     .load\@
        .endm

;;; a % \1 = a
modulo: .macro
.add\@:                         ; a が負の時?よくわかんない
        cmp     #0
        bpl     .sub\@
        clc
        adc     \1
        jmp     .add\@
.sub\@                          ;  a が \1 以上なら引く
        clc
        cmp     \1
        bcc     .return\@
        sec
        sbc     \1
        jmp     .sub\@
.return\@:
        .endm

;;; a << \1 = a
asln:   .macro
        ldx     #$00
.shift\@:
        cpx     \1
        beq     .return\@
        asl     a
        inx
        jmp     .shift\@
.return\@:
        .endm

;;; a >> \1 = a
lsrn:   .macro
        ldx     #$00
.shift\@:
        cpx     \1
        beq     .return\@
        lsr     a
        inx
        jmp     .shift\@
.return\@:
        .endm

;;; a << \1 >> \1 = a
dropln:  .macro
        asln    \1
        lsrn    \1
        .endm
        

;;; -----------------------
;;; 変数
 
        .org $0000
;;; ゼロページ変数
PARAM1 = $a0                    ; サブルーチンの引数に使う
PARAM2 = $a1
PARAM3 = $a2
PARAM4 = $a3
PARAM5 = $a4

KEYDATA = $f0                   ; コントローラ情報。上位ビットから A, B, select, start, up, down, left, right

        .org $0300
;;; 変数. ゼロページとの使い分けはよくわからない
CursorX:        .db     0       ; カーソルのX位置
CursorY:        .db     0       ; カーソルのY位置
KeyResponseCount:       .db     0 ; 毎フレーム移動すると早すぎるので、適度にスキップする用カウンタ


;;; -----------------------
;;; メインプログラム

        .org $8000
start:
        jsr     init
        jsr     main


;;; -----------------------
;;; サブルーチン        

;;; 初期化
init:   
        waitVBlank

        ;;  PPU 初期化
        lda     #%00010000
        sta     $2000
        lda     #%00000110      ; スプライトと BG の表示を OFF にしておく
        sta     $2001

        loadPalette     palette ; カラーパレットロード
        jsr     drawCharTable   ; ひらがな表描画

        ;; カーソル位置初期化
        lda     #0
        sta     CursorX
        sta     CursorY
        jsr     setCursor

        lda     #$00
        sta     KeyResponseCount ; キーに反応するフレームカウント初期化
        sta     <KEYDATA

        ;;  スクロール設定
        lda     #$00
        sta     $2005
        sta     $2005

        lda     #%00011110       ;  スプライトと BG の表示を ON
        sta     $2001
        rts

;;; ひらがな表描画
drawCharTable:
        lda     #$80            ; あ
        sta     <PARAM1

        ;; 位置設定
        lda     #$21
        sta     <PARAM2
        lda     #$65
        sta     <PARAM3

        ;; あ行 - な行
        lda     #$05
        sta     <$00            ; <$00 行カウンタ
.aNo:
        ;; drawCharLine([あ-な], <PARAM2, <PARAM3, 5, 1)
        lda     #$05
        sta     <PARAM4
        lda     #$01
        sta     <PARAM5
        jsr     drawCharLine

        jsr     down            ; 1行下に移動

        dec     <$00
        bne     .aNo

        ;; っ ゃ ゅ ょ ゛
        lda     <PARAM2
        sta     $2006
        lda     <PARAM3
        sta     $2006
        drawc   #$b3            ; っ
        drawc   #$00
        drawc   #$b4            ; ゃ
        drawc   #$00
        drawc   #$b5            ; ゅ
        drawc   #$00
        drawc   #$b6            ; ょ
        drawc   #$00
        drawc   #$b7            ; ゛

        ;; は行
        lda     #$99
        sta     <PARAM1
        lda     #$21
        sta     <PARAM2
        lda     #$71
        sta     <PARAM3
        jsr     drawCharLine

        jsr     down

        ;; ま行
        jsr     drawCharLine

        jsr     down

        ;; や行
        lda     #$03
        sta     <PARAM4
        lda     #$03
        sta     <PARAM5
        jsr     drawCharLine

        jsr     down

        ;; ら行
        lda     #$05
        sta     <PARAM4
        lda     #$01
        sta     <PARAM5
        jsr     drawCharLine

        jsr     down

        ;; わ行
        lda     #$03
        sta     <PARAM4
        lda     #$03
        sta     <PARAM5
        jsr     drawCharLine

        jsr     down

        ;;  ゜, もどる
        lda     <PARAM2
        sta     $2006
        lda     <PARAM3
        sta     $2006
        drawc   #$b8            ; ゜
        drawc   #$00
        drawc   #$00
        drawc   #$00
        drawc   #$a2            ; も
        drawc   #$93            ; と
        drawc   #$b7            ; ゛
        drawc   #$a8            ; る
        
        rts

;;; <PARAM2, <PARAM3 を VRAM アドレスとして、1行下に移動する
down:
        lda     <PARAM3
        clc                     ; キャリーフラグクリア
        adc     #$40
        sta     <PARAM3
        bcc     .return         ; キャリーチェック
        inc     <PARAM2
.return:
        rts

;;; ひらがな行描画(あ行、か行など)
;;; params char, pos1, pos2, length, interval
drawCharLine:
        ;; 描画位置設定
        lda     <PARAM2
        sta     $2006
        lda     <PARAM3
        sta     $2006

        ldx     <PARAM4
.char:
        dex
        bmi     .return

        drawc   <PARAM1            ; 文字描画
        inc     <PARAM1            ; 次の文字に

        ;; 文字間のスペース
        ldy     <PARAM5
.interval:
        dey
        bmi     .char
        drawc   #$00
        jmp     .interval

.return
        rts



;;; main loop
main:
        waitVBlank
        jsr     readPad
        jsr     moveCursor
        jmp     main


;;; コントローラ情報を読む
readPad:
        ;; 初期化
        lda     #$01
        sta     $4016
        lda     #$00
        sta     $4016

        ;; ボタンを1個読んではshift
        ldx     #$08
        rol     <KEYDATA        ; キャリー分左ローテート
.readButton
        lda     $4016
        and     #$01
        rol     <KEYDATA        ; 左ローテート
        ora     <KEYDATA
        sta     <KEYDATA
        dex
        bne     .readButton

        rts


;;; カーソルを入力に合わせて移動
;;; ボタンを押し始めた時、または
;;; KeyResponseCountが0の時に移動
moveCursor:
        lda     <KEYDATA
        and     #%00001111
        bne     .pressed

.unpressed                      ; ボタンが押されてない時
        lda     #0
        sta     KeyResponseCount ; 待ちカウントをクリアして次回押されるのを待機
        rts

.pressed                        ; ボタンが押されてる時
        lda     KeyResponseCount
        bne     .continue

.first                          ; 今押さればかりの時
        jmp     .move           ; 無条件で移動
        
.continue                       ; 前フレームから押されていた時
        dec     KeyResponseCount
        beq     .move           ; 待ちカウントが0になったときだけ移動

        lda     #0              ; 0でないときは待機
        sta     <KEYDATA
        rts

.move                           ; <KEYDATA を読み取ってカーソルを移動
        lda     #$10
        sta     KeyResponseCount ; 待ちカウント初期化

        lda     <KEYDATA
        and     #%00001000
        bne     .up

        lda     <KEYDATA        
        and     #%00000100
        bne     .down

        lda     <KEYDATA        
        and     #%00000010
        bne     .left

        lda     <KEYDATA
        and     #%00000001
        bne     .right

        rts

.up:
        dec     CursorY
        jsr     validateCursor
        cmp     #0
        beq     .up
        jsr     setCursor
        rts

.down:
        inc     CursorY
        jsr     validateCursor
        cmp     #0
        beq     .down
        jsr     setCursor
        rts

.left:
        dec     CursorX
        jsr     validateCursor
        cmp     #0
        beq     .left
        jsr     setCursor
        rts

.right:
        inc     CursorX
        jsr     validateCursor
        cmp     #0
        beq     .right
        jsr     setCursor
        rts
        
;;; CursorX, CursorY に合わせてカーソルスプライトを表示
setCursor:
        
        lda     #$00            ; カーソル画像のアドレス
        sta     $2003

        ;; Y
        lda     CursorY
        asln    #$04
        clc
        adc     #86
        sta     $2004

        lda     #$00
        sta     $2004
        sta     $2004

        ;; X
        lda     CursorX
        asln    #$04
        clc
        adc     #31
        sta     $2004

        ;; KEYDATA クリア
        lda     #$00
        sta     <KEYDATA

        rts

;;; CursorX, CursorY にちゃんとした位置が入っていれば #1,
;;; そうでなければ #0 を a に入れる
validateCursor:
        lda     CursorY
        modulo  #$06
        sta     CursorY

        lda     CursorX
        modulo  #$0b
        sta     CursorX        

        cmp     #$05            ;  真ん中の空白列はだめ
        beq     .invalid

        cmp     #$07            ; ひ列
        beq     .hihe
        cmp     #$09            ; へ列
        beq     .hihe

        cmp     #$0a            ; 最終列
        beq     .last

        jmp     .valid        

.hihe                           ; ひ・へ列の時
        lda     CursorY
        cmp     #$02            ; や行はだめ
        beq     .invalid
        cmp     #$04            ; わ行以降はだめ
        bcs     .invalid
        jmp     .valid

.last                           ; 最終列の時
        lda     CursorY
        cmp     #$05            ; 最終行はだめ
        beq     .invalid
        jmp     .valid

.valid
        lda     #1
        rts   
.invalid
        lda     #0
        rts


;;;  カラーパレット
palette:
        .byte   $0f, $00, $10, $20
        .byte   $0f, $00, $10, $20
        .byte   $0f, $00, $10, $20
        .byte   $0f, $00, $10, $20
        .byte   $0f, $00, $10, $20
        .byte   $0f, $00, $10, $20
        .byte   $0f, $00, $10, $20
        .byte   $0f, $00, $10, $20

ファミコンプログラミング


アセンブラがことのほか難しい

;;; use nesasm
;;; http://alohakun.blog7.fc2.com/blog-entry-909.html を下地に作成

        .inesprg 1            ;  プログラムに使うバンクの数
        .ineschr 1            ;  CHR データに使うバンクの数
        .inesmir 0            ;  水平ミラーリング
        .inesmap 0            ;  マッパー

;;; -----------------------
;;;  データバンク
;;; -----------------------

        .bank 2
        .org $0000
        
        ;; フォントデータのパターンテーブルを埋め込む
        ;; http://hp.vector.co.jp/authors/VA042397/nes/sample.html
        .incbin  "character.chr"


;;; -----------------------
;;;  プログラムバンク
;;; -----------------------
        .bank 0
        .org $8000

;;; start!
start:
        jsr     init
        jsr     main
;;; end!


;;; VBlank待ちマクロ
waitVBlank:     .macro
.wait\@:
        lda     $2002           ; VBlankが発生し,$2002の7ビット目が1になるまで待機
        bpl     .wait\@
        .endm


;;; 文字描画マクロ
;;; @params char
drawc:  .macro
        lda     \1
        sta     $2007
        .endm

;;; カラーパレット読み込みマクロ
;;; @params palette, length
loadPalette:    .macro
        lda     #$3f
        sta     $2006
        lda     #$00
        sta     $2006

        ldx     #$00
.load\@:
        lda     \1, x
        sta     $2007        
        inx
        cpx     \2
        bne     .load\@
        .endm        

;;; 初期化
init:   
        waitVBlank

        ;;  PPU 初期化
        lda     #%00001000
        sta     $2000
        lda     #%00000110      ; スプライトと BG の表示を OFF にしておく
        sta     $2001

        loadPalette     wordPalette, #$10 ; 文字用パレットロード

        jsr     drawCharTable   ; ひらがな表描画

        ;;  スクロール設定
        lda     #$00
        sta     $2005
        sta     $2005

        lda     #%00011110       ;  スプライトと BG の表示を ON
        sta     $2001
        rts

;;; ひらがな表描画
drawCharTable:
        lda     #$80            ; あ
        sta     <$00            ; $00 文字

        ;; 位置設定
        lda     #$21
        sta     <$01            ; $01 pos1
        lda     #$65
        sta     <$02            ; $02 pos2

        ;; あ行 - な行
        lda     #$05
        sta     <$10            ; $10 行カウンタ
.aNo:
        ;; drawCharLine([あ-な], <$01, <$02, 5, 1)
        lda     #$05
        sta     <$03
        lda     #$01
        sta     <$04
        jsr     drawCharLine

        jsr     down            ; 1行下に移動

        dec     <$10
        bne     .aNo

        ;; っ ゃ ゅ ょ ゛
        lda     <$01
        sta     $2006
        lda     <$02
        sta     $2006
        drawc   #$b3            ; っ
        drawc   #$00
        drawc   #$b4            ; ゃ
        drawc   #$00
        drawc   #$b5            ; ゅ
        drawc   #$00
        drawc   #$b6            ; ょ
        drawc   #$00
        drawc   #$b7            ; ゛

        ;; は行
        lda     #$99
        sta     <$00
        lda     #$21
        sta     <$01
        lda     #$71
        sta     <$02
        jsr     drawCharLine

        jsr     down

        ;; ま行
        jsr     drawCharLine

        jsr     down

        ;; や行
        lda     #$03
        sta     <$03
        lda     #$03
        sta     <$04
        jsr     drawCharLine

        jsr     down

        ;; ら行
        lda     #$05
        sta     <$03
        lda     #$01
        sta     <$04
        jsr     drawCharLine

        jsr     down

        ;; わ行
        lda     #$03
        sta     <$03
        lda     #$03
        sta     <$04
        jsr     drawCharLine

        jsr     down

        ;;  ゜, もどる
        lda     <$01
        sta     $2006
        lda     <$02
        sta     $2006
        drawc   #$b8            ; ゜
        drawc   #$00
        drawc   #$00
        drawc   #$00
        drawc   #$00
        drawc   #$a2            ; も
        drawc   #$93            ; と
        drawc   #$b7            ; ゛
        drawc   #$a8            ; る
        
        rts

;;; <$01, <$02 を VRAM アドレスとして、1行下に移動する
down:
        lda     <$02
        clc                     ; キャリーフラグクリア
        adc     #$40
        sta     <$02
        bcc     .return         ; キャリーチェック
        inc     <$01
.return:
        rts

;;; ひらがな行描画(あ行、か行など)
;;; params char, pos1, pos2, length, interval
drawCharLine:
        ;; 描画位置設定
        lda     <$01
        sta     $2006
        lda     <$02
        sta     $2006

        ldx     <$03
.char:
        dex
        bmi     .return

        drawc   <$00            ; 文字描画
        inc     <$00            ; 次の文字に

        ;; 文字間のスペース
        ldy     <$04
.interval:
        dey
        bmi     .char
        drawc   #$00
        jmp     .interval

.return
        rts



;;; main loop
main:
        waitVBlank
        jmp main


;;;  文字色パレットテーブル
wordPalette:
        .byte   $0f, $00, $10, $20
        .byte   $0f, $06, $16, $26
        .byte   $0f, $08, $18, $28
        .byte   $0f, $0a, $1a, $2a


;;; -----------------------
;;;  割り込みハンドラバンク
;;; -----------------------

        .bank 1
        .org $FFFA

        .dw 0                 ;  VBlank割り込みハンドラ
        .dw start             ;  リセット割り込みハンドラ
        .dw 0                 ;  ハードウェア/ソフトウェア割り込みハンドラ

Objective-C から C++ の仮想関数を呼ぶ

仮想メンバ関数がある場合、当該C++クラスはObjective-Cインスタンス変数として機能しません。

#import <Cocoa/Cocoa.h>
 
struct Class0 { void foo(); };
struct Class1 { virtual void foo(); };
struct Class2 { Class2(int i, int j); };
 
@interface Foo :NSObject {
    Class0 class0;      // OK
    Class1 class1;      // エラー!
    Class1 *ptr;        // OK&#8212;Fooのinitから'ptr = new Class1()'を呼び出し、
                        // Fooのdeallocから'delete ptr'を呼び出す
    Class2 class2;      // 警告&#8212;コンストラクタを呼び出さない!
...
@end

C++では、仮想関数を含むクラスの各インスタンスが、適切な仮想関数テーブルポインタを含む必要があります。しかし、Objective-CランタイムはC++オブジェクトモデルを知らないため、仮想関数テーブルポインタを初期化することができません。同様に、Objective-Cランタイムは、それらのオブジェクトのC++コンストラクタまたはデストラクタに対して呼び出しを発行できません。C++クラスにユーザ定義コのンストラクタやデストラクタがあっても、それらは呼び出されません。そのような場合、コンパイラは警告を発します。

日本語ドキュメント - Apple Developer

とあるのだが、C++オブジェクトをポインタで持つなら問題ないの?仮想関数を呼んでも大丈夫なの?というのがよくわからないのでテスト。

#import <Foundation/NSObject.h>
#import <stdio.h>

// C++ 親クラス
class Parent {
  public:
    virtual void sayHello() { printf("hello, I'm C++ parent.\n"); }
    virtual ~Parent() { printf("parent destructor\n"); }
};

// C++ 子クラス
class Child : public Parent {
  public:
    virtual void sayHello() { printf("hello, I'm C++ child.\n"); }
    virtual ~Child() { printf("child destructor\n"); }
};

// Objective-C クラス
@interface ObjC : NSObject {
    Parent *cppObject;  // 親クラスのポインタとして保持
}
- (void)sayHello;
@end
@implementation ObjC
- (id)init {
    if (self = [super init]) {
        cppObject = new Child();  // 子クラスのオブジェクトを作成
    }
    return self;
}
- (void)dealloc {
    delete cppObject;
    [super dealloc];
}
- (void)sayHello { cppObject->sayHello(); }
@end

int main(int argc, char *argv[])
{
    id objc = [[ObjC alloc] init];
    [objc sayHello];
    [objc release];
}

これを実行。

$ gcc -o main main.mm -framework Foundation -lstdc++
$ ./main
hello, I'm C++ child.
child destructor
parent destructor

期待通り動いてる。
解釈すると、C++オブジェクトの動作は 'Objective-Cランタイム' の外で行われている。Objectice-Cランタイムに暗黙的なC++オブジェクトの生成と破棄をさせなければ、つまりC++オブジェクトを全てポインタで持つのであれば、仮想関数があっても問題はない。ということだと思います。
'Call C++ Default Ctors/Dtors in Objective-C' を on にした時の挙動は調べないとわからない。

URLエンコードのデコード

http://blog.zakura.jp/cal/2008/04/javaurldecoder.html

修正したバージョンをあげている人が見つからなかったので書いた。

import java.io.*;
import java.util.*;

public class URLDecoder {

    public static String decode(String s) throws UnsupportedEncodingException {
        return decode(s, "JISAutoDetect");
    }

    public static String decode(String s, String enc) throws UnsupportedEncodingException {

        boolean needToChange = false;
        int numChars = s.length();
        int i = 0;
        char c;
        byte[] bytes = new byte[numChars];
        int bytePos = 0;

        while (i < numChars) {
            c = s.charAt(i);
            switch (c) {
            case '+':
                bytes[bytePos++] = (byte)' ';
                i++;
                break;
            case '%':
                if (i + 3 > numChars)
                    throw new IllegalArgumentException("URLDecoder: Incomplete trailing escape (%) pattern");

                try {
                    bytes[bytePos++] = (byte)Integer.parseInt(s.substring(i + 1, i + 3), 16);
                } catch (NumberFormatException e) {
                    throw new IllegalArgumentException("URLDecoder: Illegal hex characters in escape (%) pattern - " + e.getMessage());
                }
                i += 3;
                break;
            default:
                bytes[bytePos++] = (byte)c;
                i++;
                break;
            }
        }

        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes, 0, bytePos), enc));
            StringBuilder buffer = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                buffer.append(line);
            }
            return buffer.toString();
        }  catch (IOException e) {
            throw new IllegalArgumentException("URLDecoder: something wrong - " + e.getMessage());
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException e) {
            }
        }

    }

}

自分用の用途でしか動作確認をしていないので気をつけてください。JISAutoDetect は UTF を認識してくれないのがかなりいまいち。Java の日本語周りのちゃんとしたライブラリってないのかな。使えば使うほど嫌いになるわ、じゃばじゃば。