Stoic Sounds 元はExtroseが運営する個人サイト名

プロフィール

顔写真(V)

Extrose
2002年頃から電脳海にいる。 制作ペースは激減したものの今でも現役の作曲者(自称)であり、機会があればBMSも作る。 が、最近はVを被ってゲーム実況に勤しんでいる。 興味があるものになんでも手を出すのでかなりの趣味を抱えている。

柊 雷夜
ユーチューブ地方で見かけるVのすがた (↑)。 たまに VRChat にも出る。 VRoid Studio 製。

リリース

個人活動

読み物

Stepmania パッケージ「Panzer Force」 - PF5thについていた独自プログラムについて Part.2

続いた

引き続きクライアント側

クソ機能

    Private Sub TextBox1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles TextBox1.KeyDown
        If e.KeyCode = Keys.X Then
            MsgBox("ULTRA EXTREME FEVER!!", MsgBoxStyle.Information)
            UnlockEx_PressX()
        ElseIf e.KeyCode = Keys.A Then
            MsgBox("Aをより多く", MsgBoxStyle.Information)
        ElseIf e.KeyCode = Keys.J Then
            MsgBox("ジミー・ライトニング「クレイジー!!」", MsgBoxStyle.Information)
        End If
    End Sub

なんだこれ・・・
TextBox1というのはアップデート内容が表示されている領域のこと

そこでXやAやらJキーを押すとメッセージが出るようになっていた

Songs タブ

画面

収録曲のリストと、プレイのスコアを収集するもの

Public Const ScoreRoot As String = ".\..\..\data\machineprofile\stats.xml"

参照している stats.xml は machineprofile のほう
・・・てなんだっけな、Stepmania はユーザープロファイルもあって、ユーザー個別に情報も収集できるけども、
Machineprofileとすると、そのStepmania内でプレイされた全プレイヤーのスコアが乗ったはず

基本的にローカルで一人プレイになるので、これでよしとしたのであろう

    Public Sub InitScore()
        If File.Exists(ScoreRoot) Then
            csXMLInit() 'スコア情報のロード
            For I As Integer = 0 To UBound(SongDatas) Step 1
                If SongDatas(I).Title = "" Then Exit For
                For I2 As Integer = 0 To 14 Step 1
                    'ロード順:0:Single-Beginner ~4:Single-Challenge 5:Double-Beginner~      14:Solo-Challenge
                    ScoreList(I * 15 + I2) = csXMLSearch(SongDatas(I).FolderName, EnumDiff.Mode(CInt(Floor(I2 / 5))), EnumDiff.Difficult(I2 Mod 5))
                    'もし、~_Hardフォルダや_Challengeフォルダが見つかった場合、上書きする。
                    If IsNothing(csXMLSearch(SongDatas(I).FolderName & "_" & LCase(EnumDiff.Difficult(I2 Mod 5)), EnumDiff.Mode(CInt(Floor(I2 / 5))), EnumDiff.Difficult(I2 Mod 5)).Tiers) = False Then
                        ScoreList(I * 15 + I2) = csXMLSearch(SongDatas(I).FolderName & "_" & LCase(EnumDiff.Difficult(I2 Mod 5)), EnumDiff.Mode(CInt(Floor(I2 / 5))), EnumDiff.Difficult(I2 Mod 5))
                    End If
                Next
            Next
            Dim UnlockSwitch82 As Boolean = False '82 = Unconscious Mischief : Hardcoreコース(6)完走(難易度問わず)
            Dim UnlockSwitch83 As Boolean = False '83 = OUT DOOR SEX IN PARK NIGHT : Launen(10)コース完走
            Dim UnlockSwitchC15 As Boolean = False 'C15 = Light Force : Step 1(3)コース完走
            Dim UnlockSwitchC16 As Boolean = False 'C16 = Dark Force : Light Force(15)コース完走
            For I As Integer = 0 To UBound(CourseDatas) Step 1
                If CourseDatas(I).Title = "" Then Exit For
                For I2 As Integer = 0 To 8 Step 1
                    CourseScoreList(I * 9 + I2) = csXMLSearchCourse(CourseDatas(I).CrsFileName & ".crs", EnumDiff.Mode(CInt(Floor(I2 / 3))), EnumDiff.CourseDiff(I2 Mod 3))
                    If CourseDatas(I).ID = 6 And CourseScoreList(I * 9 + I2).Tiers <> "Failed" And CourseScoreList(I * 9 + I2).Tiers <> "" Then '6 空白以外、Tiers06(最低ランク)以外
                        UnlockSwitch82 = True
                    End If
                    If CourseDatas(I).ID = 10 And CourseScoreList(I * 9 + I2).Tiers <> "Failed" And CourseScoreList(I * 9 + I2).Tiers <> "" Then '10 空白以外、Tiers06(最低ランク)以外
                        UnlockSwitch83 = True
                    End If
                    If CourseDatas(I).ID = 3 And CourseScoreList(I * 9 + I2).Tiers <> "Failed" And CourseScoreList(I * 9 + I2).Tiers <> "" Then '10 空白以外、Tiers06(最低ランク)以外
                        UnlockSwitchC15 = True
                    End If
                    If CourseDatas(I).ID = 15 And CourseScoreList(I * 9 + I2).Tiers <> "Failed" And CourseScoreList(I * 9 + I2).Tiers <> "" Then '10 空白以外、Tiers06(最低ランク)以外
                        UnlockSwitchC16 = True
                    End If
                Next
            Next
            For I As Integer = 0 To UBound(WCourseDatas) Step 1
                If WCourseDatas(I).Title = "" Then Exit For
                For I2 As Integer = 0 To 2 Step 1
                    WCourseScoreList(I * 3 + I2) = csXMLSearchCourse(WCourseDatas(I).CrsFileName & ".crs", EnumDiff.Mode(I2))
                    Console.WriteLine(WCourseScoreList(I * 3 + I2).Percent & " / by " & WCourseDatas(I).CrsFileName)
                Next
            Next

            GetTotal()

            '特殊解禁
            For I As Integer = 0 To UBound(SongDatas) Step 1
                '単曲所持RP
                If SongDatas(I).UnlockNeedPlay = 600000 And SongDatas(I).UnlockNeedRP >= SongDatas(I).HasRP Then SIDUnlock(I)

                'Pure Boys (Challenge = 通称ヒットレル氏) 譜面数3
                If SongDatas(I).ID = 56 And SongDatas(I).ClearFlag >= 3 Then SIDUnlockA(I, "feat.ヒットレル氏")

                'NEW ANACHRONISM -Dream side- Score 100億  SID=81
                If SongDatas(I).ID = 81 And ScoreTotal.Score >= 10000000000 Then SIDUnlock(I)

                'NEW ANACHRONISM -Real side- HasRP=14 SID=81
                If SongDatas(I).ID = 81 And SongDatas(I).HasRP >= 14 Then SIDUnlockA(I, "Real side")

                'Unconscious Mischief : Hardcoreコース完走
                If SongDatas(I).ID = 82 And UnlockSwitch82 Then SIDUnlock(I)

                'OUT DOOR SEX IN PARK NIGHT : Launenコース完走
                If SongDatas(I).ID = 83 And UnlockSwitch83 Then SIDUnlock(I)
            Next
            For I As Integer = 0 To UBound(CourseDatas) Step 1
                'Light Force : Step 1コース完走
                If CourseDatas(I).ID = 15 And UnlockSwitchC15 Then CIDUnlock(I)

                'Dark Force : Light Force コース完走
                If CourseDatas(I).ID = 16 And UnlockSwitchC16 Then CIDUnlock(I)
            Next


        Else
            MsgBox("Stats.xmlが見つかりませんでした。スコアはロードされません。一回以上プレイした後、再度起動させてください。", MsgBoxStyle.Critical, ProgramTitle)
        End If
    End Sub

スコアを読み取る処理
エラーのトラップがIFのElse側とか不慣れ感

ここはスコア情報の読み取りというよりは、スコアの情報を用いた解禁処理と言う感じ
この解禁情報って全部明かしたんだったかどうだったか

XMLファイルの読み取り

このスコア周りを解析するためには XML を読まなきゃならんのだけども、
VB.NET主流の XmlDocumentとかはイマイチ使い勝手がよくわからんかった

例えば

<root>
  <category>
    <data>
      <value1>123</value1>
    </data>
    <data>
      <value1>234</value1>
    </data>
  </category>
</root>

こういうXMLがあったら、value1には「root/category/data/value1」みたいにアクセスしたいんだけども、
XMLDocument ではどうも直感的にこうできなかった・・・と思う
今ならわかるんだろうか

そこで、XMLファイルリーダーを自作することに・・・よく作ったな

Imports System.Xml

''' <summary>XMLファイル 読み取り/書き込み補助</summary>
''' <remarks>(C) 2010 dj Extrose</remarks>
Module ExXMLReader

    ''' <summary>データバッファ</summary>
    Public XMLData(50) As String

    ''' <summary>
    ''' XMLファイルをバッファへロードします。
    ''' </summary>
    ''' <param name="Filename">読み込むXMLファイル</param>
    Public Sub XMLLoad(ByVal Filename As String)

        Dim xmlRdr As System.Xml.XmlReader = XmlReader.Create(New IO.StreamReader(Filename, System.Text.Encoding.UTF8))

        'Txの定義と初期化。
        Dim Tx(10) As String : For I As Integer = 0 To Tx.Length - 1 Step 1 : Tx(I) = "" : Next I

        'XMLData初期化
        XMLInit()

        Dim Ic As Integer = 0
        Dim Fl As Boolean = False
        While xmlRdr.Read()
            Select Case xmlRdr.NodeType
                Case XmlNodeType.Element
                    Tx(xmlRdr.Depth) = xmlRdr.Name
                    XMLData(Ic) = MakeXMLObjectLine(Tx) & ">"
                    While xmlRdr.MoveToNextAttribute
                        XMLData(Ic) = XMLData(Ic) & xmlRdr.Name & "=" & xmlRdr.Value & ","
                        Fl = True
                    End While
                    If Fl Then Ic += 1 : Fl = False
                Case XmlNodeType.Text
                    XMLData(Ic) = MakeXMLObjectLine(Tx) & ">" & xmlRdr.Value
                    Ic += 1
                Case XmlNodeType.EndElement
                    Tx(xmlRdr.Depth) = ""
            End Select
        End While

        xmlRdr.Close()
    End Sub

    ''' <summary>
    ''' バッファに格納したデータをクリアします。
    ''' </summary>
    Public Sub XMLInit()
        For I As Integer = 0 To XMLData.Length - 1 Step 1 : XMLData(I) = "" : Next I
    End Sub

    ''' <summary>
    ''' XMLデータを検索します。引数の最初から順に検索され、最後の引数に該当する検索結果を返します。見つからなかった場合、何も返しません。
    ''' </summary>
    ''' <param name="SearchStr">検索文字列。書式「[タグパス(例:TestTag.TestTag2)]&gt;[属性名]=[値]」。属性を省略すると、タグで囲まれた値を検索します。</param>
    Public Function XMLSearchStrict(ByVal ParamArray SearchStr() As String) As String
        XMLSearchStrict = ""
        Dim I As Integer = 0, Sl As Integer = 0, Tx As String
        For Each Ss As String In SearchStr
            'あいまい検索対策
            Ss = Ss.ToUpper
            Do
                Application.DoEvents()                '重いのでDoEvents
                If XMLData(I) = "" Or XMLData.Length < I Then Exit For

                Tx = XMLData(I).ToUpper
                If Left(Tx, Ss.Length) = Ss Then
                    Sl = Ss.Length
                    Exit Do
                End If

                I += 1
            Loop
        Next Ss
        If XMLData(I) <> "" Then XMLSearchStrict = Right(XMLData(I), XMLData(I).Length - Sl)
    End Function
    ''' <summary>
    ''' XMLデータを検索します。引数の最初から順に検索され、最後の引数に該当する検索結果を返します。見つからなかった場合、何も返しません。第一引数は必須で、第一引数のタグ範囲内(終了するまで)に任意の第二引数が見つからなかった場合、何も返しません。
    ''' </summary>
    ''' <param name="SearchStr">検索文字列。書式「[タグパス(例:TestTag.TestTag2)]&gt;[属性名]=[値]」。属性を省略すると、タグで囲まれた値を検索します。</param>
    Public Function XMLSearchStrictEx(ByVal ParamArray SearchStr() As String) As String
        XMLSearchStrictEx = ""
        Dim I As Integer = 0, Sl As Integer = 0, Tx As String, I2 As Integer = UBound(XMLData)
        Dim Ti2 As Integer = 0
        'あいまい検索対策
        For Each Ss As String In SearchStr
            Ss = Ss.ToUpper
            Do
                If XMLData(I) = "" Or I > I2 Then Exit For

                Tx = XMLData(I).ToUpper
                If Left(Tx, Ss.Length) = Ss Then
                    Ti2 = I + 1
                    Sl = Ss.Length
                    Exit Do
                End If

                I += 1
            Loop

            '第二ループ:終点探査

            Do
                If XMLData(Ti2) = "" Or Ti2 > I2 Then Exit Do
                Tx = XMLData(Ti2).ToUpper

                Dim Ttx As String = "", Addes As String = ""
                '属性指定まで含む場合
                If InStr(Ss, "=") <> 0 Then Ttx = Split(Ss, "=")(0) : Addes = "="
                '属性指定を含まない場合
                If InStr(Ss, "=") = 0 Then Ttx = Split(Ss, ">")(0) : Addes = ">"

                If Left(Tx, Ttx.Length + 1) = Ttx & Addes Then
                    I2 = Ti2 - 1
                    Exit Do
                End If
                Ti2 += 1
            Loop
        Next

        If XMLData(I) <> "" And _
           Left(XMLData(I), SearchStr(UBound(SearchStr)).Length) = SearchStr(UBound(SearchStr)) Then
            XMLSearchStrictEx = Right(XMLData(I), XMLData(I).Length - Sl)
        End If

    End Function

    ''' <summary>
    ''' XMLデータを検索します。引数の最初から順に検索され、最後の引数に該当する検索結果を複数返します。見つからなかった場合、何も返しません。第一引数は必須で、第一引数のタグ範囲内(終了するまで)に任意の第二引数が見つからなかった場合、何も返しません。
    ''' </summary>
    ''' <param name="SearchStr">検索文字列。書式「[タグパス(例:TestTag.TestTag2)]&gt;[属性名]=[値]」。属性を省略すると、タグで囲まれた値を検索します。</param>
    Public Function XMLSearchMulti(ByVal ParamArray SearchStr() As String) As String()
        XMLSearchMulti = Nothing
        Dim I As Integer = 0, Sl As Integer = 0, Tx As String, I2 As Integer = UBound(XMLData)
        Dim Ti2 As Integer = 0
        'あいまい検索対策
        For Each Ss As String In SearchStr
            If Ss = SearchStr(UBound(SearchStr)) Then Exit For '最後の要素ならループ抜け
            Ss = Ss.ToUpper
            Do
                If XMLData(I) = "" Or I > I2 Then Exit For

                Tx = XMLData(I).ToUpper
                If Left(Tx, Ss.Length) = Ss Then
                    Ti2 = I + 1
                    Sl = Ss.Length
                    Exit Do
                End If

                I += 1
            Loop

            '第二ループ:終点探査

            Do
                If XMLData(Ti2) = "" Or Ti2 > I2 Then Exit Do
                Tx = XMLData(Ti2).ToUpper

                Dim Ttx As String = "", Addes As String = ""
                '属性指定まで含む場合
                If InStr(Ss, "=") <> 0 Then Ttx = Split(Ss, "=")(0) : Addes = "="
                '属性指定を含まない場合
                If InStr(Ss, "=") = 0 Then Ttx = Split(Ss, ">")(0) : Addes = ">"

                If Left(Tx, Ttx.Length + 1) = Ttx & Addes Then
                    I2 = Ti2 - 1
                    Exit Do
                End If
                Ti2 += 1
            Loop
        Next

        Dim ResMulti As String(), ResTemp As String = ""
        For Ia As Integer = I To I2 Step 1
            Dim Ss As String = SearchStr(UBound(SearchStr))
            Ss = Ss.ToUpper
            Tx = XMLData(I).ToUpper
            If Left(Tx, Ss.Length) = Ss Then
                ResTemp = ResTemp & Right(XMLData(I), XMLData(I).Length - Ss.Length) & "<=>"
            End If
        Next

        If ResTemp.Length <> 0 Then ResTemp = Left(ResTemp, ResTemp.Length - 1) 'カンマ取り
        ResMulti = Split(ResTemp, "<=>")

        If ResTemp <> "" Then
            XMLSearchMulti = ResMulti
        End If

    End Function

    ''' <summary>
    ''' XMLデータを検索します。引数の最初から順に検索され、最後の引数に該当する検索結果を返します。見つからなかった場合、何も返しません。
    ''' </summary>
    ''' <param name="SearchStr">検索文字列。</param>
    Public Function XMLSearch(ByVal ParamArray SearchStr() As String) As String
        XMLSearch = ""
        Dim I As Integer = 0, Sl As Integer = 0, Tx As String
        For Each Ss As String In SearchStr
            'あいまい検索対策
            Ss = Ss.ToUpper
            Do
                Application.DoEvents()                '重いのでDoEvents
                If XMLData(I) = "" Or XMLData.Length < I Then Exit For

                Tx = XMLData(I).ToUpper
                If InStr(Tx, Ss) <> 0 Then
                    Sl = Ss.Length
                    Exit Do
                End If

                I += 1
            Loop
        Next Ss
        If XMLData(I) <> "" Then XMLSearch = Split(XMLData(I), "=")(1)
    End Function

    Public Function XMLDataOutput() As Boolean
        Try
            Dim Sw As New System.IO.StreamWriter(".\XMLReaderDebug.log")
            For Each St As String In XMLData
                If St = "" Then Exit For
                Sw.WriteLine(St)
            Next St
            Sw.Close()
            XMLDataOutput = True
        Catch ex As Exception
            XMLDataOutput = False
        End Try
    End Function


    ''' <summary>
    ''' バッファサイズを変更します。
    ''' </summary>
    ''' <param name="Size">バッファサイズ。省略した場合、100に設定します。</param>
    Public Sub XMLResizeBuffer(Optional ByVal Size As Integer = 50)
        ReDim XMLData(Size)
        For I As Integer = 0 To Size Step 1 : XMLData(I) = "" : Next I
    End Sub

    Private Function MakeXMLObjectLine(ByVal TagDatas As String()) As String
        MakeXMLObjectLine = ""
        For I As Integer = 0 To TagDatas.Length - 1 Step 1
            If TagDatas(I) = "" Then Exit For
            MakeXMLObjectLine = MakeXMLObjectLine & TagDatas(I) & "."
        Next
        '末端チョッパー
        MakeXMLObjectLine = Left(MakeXMLObjectLine, MakeXMLObjectLine.Length - 1)
    End Function
End Module

今ならもっと利便性のあるオブジェクトが存在するんだろうか
WindowsのGUIプログラム作るってなった時に、今やVB.netを選択することも無いだろうし
もっぺん勉強しようとはあんまり

で、このコードの仕様についてはもう覚えていないので、こういうときの ChatGPT
仕様書を起こしてもらった

ちなみに2025年現在、このページ含め当サイトは markdown でページを作っている
ChatGPTに出させるとまずmarkdownで出すので、コードブロックで読めるようにと要望した

ExXMLReader モジュール仕様書
==========================

作成者: dj Extrose
作成年: 2010年

概要:
  XMLファイルの読み込みおよび検索機能を提供するVB.NETモジュールです。
  読み込んだXMLはバッファ(XMLData)に保存され、階層構造付きで検索可能になります。

■ グローバル変数

- XMLData(50) As String
    XMLの内容を格納する配列バッファ。初期サイズは50。
    XMLResizeBufferによりサイズ変更可能。

■ 関数一覧

- Sub XMLLoad(Filename As String)
    XMLファイルを読み込み、階層情報とともにXMLDataに格納します。
    属性は「タグパス>属性=値,」の形式で保存されます。

- Sub XMLInit()
    バッファXMLDataを初期化(空文字に)します。

- Function XMLSearchStrict(ParamArray SearchStr() As String) As String
    指定されたタグパスと属性条件に一致するデータを厳密に1つ検索します。
    例: "Root.Child>id=abc" のような文字列で検索。

- Function XMLSearchStrictEx(ParamArray SearchStr() As String) As String
    XMLSearchStrictに似ていますが、範囲内(先頭タグ〜終了タグ)にあるかを判定。
    先頭タグで囲まれた中に最終条件がない場合、何も返しません。

- Function XMLSearchMulti(ParamArray SearchStr() As String) As String()
    複数マッチを検索して配列で返す。
    先頭タグの範囲内に最終条件に一致するものを探し、すべて返却。

- Function XMLSearch(ParamArray SearchStr() As String) As String
    あいまい検索で、最初に一致した要素の属性値を返します。
    検索文字列が属性の場合、"属性=値"から値部分だけを返します。

- Function XMLDataOutput() As Boolean
    XMLDataの中身をファイル「./XMLReaderDebug.log」に出力。
    成功時はTrue、失敗時はFalseを返します。

- Sub XMLResizeBuffer(Optional Size As Integer = 50)
    XMLDataのサイズを変更し、すべて初期化します。

■ 内部関数

- MakeXMLObjectLine(TagDatas As String()) As String
    タグ配列を「Root.Child.GrandChild」のように「.」で結合し、階層表現を生成。

■ バッファに格納されるデータ形式(例)

    Root>id=abc,
    Root.Child>テキスト
    Root.Child.GrandChild>type=main,text=hello

  - タグの階層は「.」で区切られます。
  - 属性は「>属性=値,」として記録。
  - テキストノードは「>テキスト値」として格納。

■ 注意点・仕様補足

- 大文字小文字は無視して比較(内部処理でUpperCase変換)
- XMLDataのサイズは初期値50、必要に応じて変更可能
- 検索関数はDoEventsを含むため、UIがあるアプリでは応答に注意
- 属性の区切りはカンマ(,)ですが、末尾には付かないこともあります
- XMLのネスト構造をフラットな文字列で保持しているため、厳密な構文解析ではない

ChatGPT すごいなあって思うと同時に、xmlを絶対に自分の使い勝手の良い読み方をしてやるという執念を感じる
加えて、オリジナルパーサが作れるということは規格化されていてこそだなとも

このオリジナルパーサを用いてstats.xml を読み取ったという

stats.xml の解析

        ExXMLReader.XMLLoad(ScoreRoot)

まずxmlファイルを読み込む

    ''' <summary>複数のデータから最も高いハイスコアを取得します。</summary>
    ''' <param name="SongFolder">「カテゴリフォルダ/曲フォルダ」で指定します。</param>
    ''' <param name="Style"></param>
    ''' <param name="Difficulty"></param>
    Public Function csXMLSearch(ByVal SongFolder As String, ByVal Style As String, ByVal Difficulty As String) As ScoreBoard
        csXMLSearch = Nothing
        Dim I As Integer = 0
        '第1ループ:曲名探し
        Do
            If ExXMLReader.XMLData(I) = "" Then Exit Function
            If ExXMLReader.XMLData(I) = "Stats.SongScores.Song>Dir=Songs/Panzer Force 5thmix/" & SongFolder & "/," Then Exit Do
            I += 1
        Loop
        '第2ループ:スタイル/難易度探し I+1し維持
        I += 1
        Do
            If ExXMLReader.XMLData(I) = "Stats.SongScores.Song.Steps>StepsType=" & Style & ",Difficulty=" & Difficulty & "," Then Exit Do
            If ExXMLReader.XMLData(I) = "" Then Exit Function
            'Stats.SongScores.Song で始まっていない行が見つかったらそこで終了
            If Left(ExXMLReader.XMLData(I), 21) <> "Stats.SongScores.Song" Then Exit Function
            'もし、曲名を発見してしまったらExit。対象文字は多分31字
            If Left(ExXMLReader.XMLData(I), 32) = "Stats.SongScores.Song>Dir=Songs/" Then Exit Function
            I += 1
        Loop

        '今IはSong.Stepsに居るので、二つ進め、HighScore.Name を取る (一つ先はNumTimesPlayed)
        I += 1
        Dim Pln As Integer = CInt(Split(ExXMLReader.XMLData(I), ">")(1))
        I += 1
        '第3ループ:新旧比較、スコア抽出
        Dim Ds As ScoreBoard = ScoreBoardInit(), Dd As ScoreBoard = ScoreBoardInit()
        Do
            If ExXMLReader.XMLData(I) = "" Then Exit Do
            '終了条件はStepsが見えるかSongsが見えるか。 前者37字 後者31字。
            If Left(ExXMLReader.XMLData(I), 38) = "Stats.SongScores.Song.Steps>StepsType=" Or _
               Left(ExXMLReader.XMLData(I), 32) = "Stats.SongScores.Song>Dir=Songs/" Then Exit Do
            'Stats.SongScores.Song で始まっていない行が見つかったらそこで終了
            If Left(ExXMLReader.XMLData(I), 21) <> "Stats.SongScores.Song" Then Exit Do
            '順序に取る
            If Left(ExXMLReader.XMLData(I), 57) = "Stats.SongScores.Song.Steps.HighScoreList.HighScore.Name>" Then I += 1 'Nameは存在したりしなかったりスルーらしい(ドヤ
            Ds.Tiers = Split(ExXMLReader.XMLData(I), ">")(1) : I += 1           'Grade
            Ds.Score = CLng(Split(ExXMLReader.XMLData(I), ">")(1)) : I += 1     'Score
            Ds.Percent = CSng(Split(ExXMLReader.XMLData(I), ">")(1)) : I += 1   'PercentDP
            Ds.SurviveSeconds = CSng(Split(ExXMLReader.XMLData(I), ">")(1)) : I += 1 'SurviveSeconds:スルー
            If Left(ExXMLReader.XMLData(I), 62) = "Stats.SongScores.Song.Steps.HighScoreList.HighScore.Modifiers>" Then _
                Ds.Modifiers = Split(ExXMLReader.XMLData(I), ">")(1) : I += 1 'Modifiers : 多分、ノーオプションだと項目が無い
            Ds.DateTime = Split(ExXMLReader.XMLData(I), ">")(1) : I += 1        'DateTime
            If Left(ExXMLReader.XMLData(I), 63) = "Stats.SongScores.Song.Steps.HighScoreList.HighScore.PlayerGuid>" Then I += 1 'PlayerGuidは存在したりしなかったりするらしい
            I += 2                                                              'MachineGuid,ProductID スルー
            I += 7                                                              'TapNoteScores 計7要素スルー
            I += 2                                                              'HoldNoteScores 計2要素スルー
            I += 10                                                             'RaderValues 計10要素スルー

            'スコア比較 DdよりDsの方が大きい場合、DdをDsに置き換え
            '追加条件:ModifierにC**0を含まない場合のみ置き換え (Cの位置が1なら、+3した4の位置が0でアウト) / Cross等が気になるが、オプション記述順からして多分問題ない・・・はず
            '2011/08/02 追加条件:Modifiersが空白でなければ。
            If Ds.Percent > Dd.Percent Then
                'エラー回避
                If Ds.Modifiers <> "" And Ds.Modifiers.Length >= 4 Then
                    If Ds.Modifiers.Substring(Ds.Modifiers.IndexOf("C") + 3, 1) <> "0" Then 'Cxx0を含んでいない場合
                        Dd = Ds
                    End If
                Else 'Modifierが空白だった場合
                    Dd = Ds
                End If
            End If
        Loop
        Dd.Totalplay = Pln
        csXMLSearch = Dd
    End Function

stats.xml というのは曲⇒スタイル/難易度 の順で登場する

スタイル/難易度だけで検索してしまうと、その要素がどの曲かはそこに情報が無く、どの曲のものかわからない
そこで、直前にある曲名情報を引いて、次に曲を探し、そこから更にスコアのある行を検索する、という順序を意識した読み方をする

曲名
  スタイル/難易度
    スコア
  スタイル/難易度
  スタイル/難易度
曲名                      ← ループ1
  スタイル/難易度          ← ループ2
    スコア                ← ループ3
  スタイル/難易度
   :  

ところで除外条件にしてある「Cxx0」というのは、beatmania IIDX とかでいうハイスピ固定のもの
一部の曲というか譜面はソフランを攻略してこそというのもあるので、それを使ったスコアは除外したかった様子
・・・ただ、これCxx0でハイスコア取ってしまってたら、もう一生そのハイスコアが更新されないのでは?

後はコースタブもあるが、大体はこのスコアタブの処理の転用
というところで、今回はこんなところで