| Commit message (Collapse) | Author | Files | Lines |
|
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1404 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1306 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1305 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1302 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
- GetStats returns a TList of TStatResult now
- Free the result-list with FreeStats
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1230 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
- Replaced TSQLiteTable with the new TSQLiteUniTable. The ..UniTable-version does not retrieve data at once (this feature is not needed/wanted in most cases). The major advantage of this version is a better handling of datatypes. TSQLiteTable does not handle datatypes correctly (and even crashes) if the field-types are not one of the SQLite types (e.g. an INT(12) field will crash if it accessed by FieldAsInteger, the same applies to "integer" but the upper-case version "INTEGER" works). With TSQLiteUniTable those crashes should not occur.
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1228 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
interface
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
{$I switches.inc}
uses USongs,
USong,
SQLiteTable3;
//--------------------
//DataBaseSystem - Class including all DB Methods
//--------------------
type
TStatResult = record
Case Typ: Byte of
0: (Singer: ShortString;
Score: Word;
Difficulty: Byte;
SongArtist: ShortString;
SongTitle: ShortString);
1: (Player: ShortString;
AverageScore: Word);
2: (Artist: ShortString;
Title: ShortString;
TimesSung: Word);
3: (ArtistName: ShortString;
TimesSungtot: Word);
end;
AStatResult = Array of TStatResult;
TDataBaseSystem = class
private
ScoreDB: TSqliteDatabase;
sFilename: string;
public
property Filename: String read sFilename;
Destructor Destroy; override;
Procedure Init(const Filename: string);
procedure ReadScore(Song: TSong);
procedure AddScore(Song: TSong; Level: integer; Name: string; Score: integer);
procedure WriteScore(Song: TSong);
Function GetStats(var Stats: AStatResult; const Typ, Count: Byte; const Page: Cardinal; const Reversed: Boolean): Boolean;
Function GetTotalEntrys(const Typ: Byte): Cardinal;
Function GetStatReset: TDateTime;
end;
var
DataBase: TDataBaseSystem;
implementation
uses
IniFiles,
ULog,
StrUtils,
SysUtils;
const
cUS_Scores = 'us_scores';
cUS_Songs = 'us_songs';
cUS_Statistics_Info = 'us_statistics_info';
//--------------------
//Create - Opens Database and Create Tables if not Exist
//--------------------
Procedure TDataBaseSystem.Init(const Filename: string);
begin
Log.LogStatus('Initializing database: "'+Filename+'"', 'TDataBaseSystem.Init');
//Open Database
ScoreDB := TSqliteDatabase.Create( Filename );
sFilename := Filename;
try
//Look for Tables => When not exist Create them
if not ScoreDB.TableExists( cUS_Scores ) then
begin
ScoreDB.execsql('CREATE TABLE `'+cUS_Scores+'` (`SongID` INT( 11 ) NOT NULL , `Difficulty` INT( 1 ) NOT NULL , `Player` VARCHAR( 150 ) NOT NULL , `Score` INT( 5 ) NOT NULL );');
debugWriteln( 'TDataBaseSystem.Init - CREATED US_Scores' );
end;
if not ScoreDB.TableExists( cUS_Songs ) then
begin
ScoreDB.execsql('CREATE TABLE `'+cUS_Songs+'` (`ID` INTEGER PRIMARY KEY, `Artist` VARCHAR( 255 ) NOT NULL , `Title` VARCHAR( 255 ) NOT NULL , `TimesPlayed` int(5) NOT NULL );');
debugWriteln( 'TDataBaseSystem.Init - CREATED US_Songs' );
end;
if not ScoreDB.TableExists( cUS_Statistics_Info ) then
begin
ScoreDB.execsql('CREATE TABLE `'+cUS_Statistics_Info+'` (`ResetTime` VARCHAR(17) );');
ScoreDB.execsql('INSERT INTO `'+cUS_Statistics_Info+'` (`ResetTime`) VALUES ("'+datetimetostr(now)+'");');
debugWriteln( 'TDataBaseSystem.Init - CREATED US_Statistics_Info' );
end;
//Not possible because of String Limitation to 255 Chars //Need to rewrite Wrapper
{if not ScoreDB.TableExists('US_SongCache') then
ScoreDB.ExecSQL('CREATE TABLE `US_SongCache` (`Path` VARCHAR( 255 ) NOT NULL , `Filename` VARCHAR( 255 ) NOT NULL , `Title` VARCHAR( 255 ) NOT NULL , `Artist` VARCHAR( 255 ) NOT NULL , `Folder` VARCHAR( 255 ) NOT NULL , `Genre` VARCHAR( 255 ) NOT NULL , `Edition` VARCHAR( 255 ) NOT NULL , `Language` VARCHAR( 255 ) NOT NULL , `Creator` VARCHAR( 255 ) NOT NULL , `Cover` VARCHAR( 255 ) NOT NULL , `Background` VARCHAR( 255 ) NOT NULL , `Video` VARCHAR( 255 ) NOT NULL , `VideoGap` FLOAT NOT NULL , `Gap` FLOAT NOT NULL , `Start` FLOAT NOT NULL , `Finish` INT( 11 ) NOT NULL , `BPM` INT( 5 ) NOT NULL , `Relative` BOOLEAN NOT NULL , `NotesGap` INT( 11 ) NOT NULL);');}
finally
Log.LogInfo( cUS_Songs +' exists: ' + IfThen(ScoreDB.TableExists(cUS_Songs), 'true', 'false'),
'TDataBaseSystem.Init');
Log.LogInfo( cUS_Scores +' exists: ' + IfThen(ScoreDB.TableExists(cUS_Scores), 'true', 'false'),
'TDataBaseSystem.Init');
//ScoreDB.Free;
end;
end;
//--------------------
//Destroy - Frees Database
//--------------------
Destructor TDataBaseSystem.Destroy;
begin
Log.LogInfo('TDataBaseSystem.Free', 'TDataBaseSystem.Destroy');
freeandnil( ScoreDB );
inherited;
end;
//--------------------
//ReadScore - Read Scores into SongArray
//--------------------
procedure TDataBaseSystem.ReadScore(Song: TSong);
var
TableData: TSqliteTable;
Difficulty: Integer;
begin
if not assigned( ScoreDB ) then
exit;
//ScoreDB := TSqliteDatabase.Create(sFilename);
try
try
//Search Song in DB
TableData := ScoreDB.GetTable('SELECT `Difficulty`, `Player`, `Score` FROM `'+cUS_Scores+'` WHERE `SongID` = (SELECT `ID` FROM `us_songs` WHERE `Artist` = "' + Song.Artist + '" AND `Title` = "' + Song.Title + '" LIMIT 1) ORDER BY `Score` DESC LIMIT 15');
//Empty Old Scores
SetLength (Song.Score[0], 0);
SetLength (Song.Score[1], 0);
SetLength (Song.Score[2], 0);
while not TableData.Eof do //Go through all Entrys
begin //Add one Entry to Array
Difficulty := StrToIntDef(TableData.FieldAsString(TableData.FieldIndex['Difficulty']), -1);
if (Difficulty >= 0) AND (Difficulty <= 2) then
begin
SetLength(Song.Score[Difficulty], Length(Song.Score[Difficulty]) + 1);
Song.Score[Difficulty, high(Song.Score[Difficulty])].Name :=
TableData.FieldAsString(TableData.FieldIndex['Player']);
Song.Score[Difficulty, high(Song.Score[Difficulty])].Score :=
StrtoInt(TableData.FieldAsString(TableData.FieldIndex['Score']));
end;
TableData.Next;
end; // While not TableData.EOF
except
for Difficulty := 0 to 2 do
begin
SetLength(Song.Score[Difficulty], 1);
Song.Score[Difficulty, 1].Name := 'Error Reading ScoreDB';
end;
end;
finally
//ScoreDb.Free;
end;
end;
//--------------------
//AddScore - Add one new Score to DB
//--------------------
procedure TDataBaseSystem.AddScore(Song: TSong; Level: integer; Name: string; Score: integer);
var
ID: Integer;
TableData: TSqliteTable;
begin
if not assigned( ScoreDB ) then
exit;
//ScoreDB := TSqliteDatabase.Create(sFilename);
try
//Prevent 0 Scores from being added
if (Score > 0) then
begin
ID := ScoreDB.GetTableValue('SELECT `ID` FROM `'+cUS_Songs+'` WHERE `Artist` = "' + Song.Artist + '" AND `Title` = "' + Song.Title + '"');
if ID = 0 then //Song doesn't exist -> Create
begin
ScoreDB.ExecSQL ('INSERT INTO `'+cUS_Songs+'` ( `ID` , `Artist` , `Title` , `TimesPlayed` ) VALUES (NULL , "' + Song.Artist + '", "' + Song.Title + '", "0");');
ID := ScoreDB.GetTableValue('SELECT `ID` FROM `US_Songs` WHERE `Artist` = "' + Song.Artist + '" AND `Title` = "' + Song.Title + '"');
if ID = 0 then //Could not Create Table
exit;
end;
//Create new Entry
ScoreDB.ExecSQL('INSERT INTO `'+cUS_Scores+'` ( `SongID` , `Difficulty` , `Player` , `Score` ) VALUES ("' + InttoStr(ID) + '", "' + InttoStr(Level) + '", "' + Name + '", "' + InttoStr(Score) + '");');
//Delete Last Position when there are more than 5 Entrys
if ScoreDB.GetTableValue('SELECT COUNT(`SongID`) FROM `'+cUS_Scores+'` WHERE `SongID` = "' + InttoStr(ID) + '" AND `Difficulty` = "' + InttoStr(Level) +'"') > 5 then
begin
TableData := ScoreDB.GetTable('SELECT `Player`, `Score` FROM `'+cUS_Scores+'` WHERE SongID = "' + InttoStr(ID) + '" AND `Difficulty` = "' + InttoStr(Level) +'" ORDER BY `Score` ASC LIMIT 1');
ScoreDB.ExecSQL('DELETE FROM `US_Scores` WHERE SongID = "' + InttoStr(ID) + '" AND `Difficulty` = "' + InttoStr(Level) +'" AND `Player` = "' + TableData.FieldAsString(TableData.FieldIndex['Player']) + '" AND `Score` = "' + TableData.FieldAsString(TableData.FieldIndex['Score']) + '"');
end;
end;
finally
//ScoreDB.Free;
end;
end;
//--------------------
//WriteScore - Not needed with new System; But used for Increment Played Count
//--------------------
procedure TDataBaseSystem.WriteScore(Song: TSong);
begin
if not assigned( ScoreDB ) then
exit;
try
//Increase TimesPlayed
ScoreDB.ExecSQL ('UPDATE `'+cUS_Songs+'` SET `TimesPlayed` = `TimesPlayed` + "1" WHERE `Title` = "' + Song.Title + '" AND `Artist` = "' + Song.Artist + '";');
except
end;
end;
//--------------------
//GetStats - Write some Stats to Array, Returns True if Chossen Page has Entrys
//Case Typ of
//0 - Best Scores
//1 - Best Singers
//2 - Most sung Songs
//3 - Most popular Band
//--------------------
Function TDataBaseSystem.GetStats(var Stats: AStatResult; const Typ, Count: Byte; const Page: Cardinal; const Reversed: Boolean): Boolean;
var
Query: String;
TableData: TSqliteTable;
begin
Result := False;
if not assigned( ScoreDB ) then
exit;
if (Length(Stats) < Count) then
Exit;
{Todo: Add Prevention that only Players with more than 5 Scores are Selected at Typ 2}
//Create Query
Case Typ of
0: Query := 'SELECT `Player` , `Difficulty` , `Score` , `Artist` , `Title` FROM `'+cUS_Scores+'` INNER JOIN `US_Songs` ON (`SongID` = `ID`) ORDER BY `Score`';
1: Query := 'SELECT `Player` , ROUND (Sum(`Score`) / COUNT(`Score`)) FROM `'+cUS_Scores+'` GROUP BY `Player` ORDER BY (Sum(`Score`) / COUNT(`Score`))';
2: Query := 'SELECT `Artist` , `Title` , `TimesPlayed` FROM `'+cUS_Songs+'` ORDER BY `TimesPlayed`';
3: Query := 'SELECT `Artist` , Sum(`TimesPlayed`) FROM `'+cUS_Songs+'` GROUP BY `Artist` ORDER BY Sum(`TimesPlayed`)';
end;
//Add Order Direction
If Reversed then
Query := Query + ' ASC'
else
Query := Query + ' DESC';
//Add Limit
Query := Query + ' LIMIT ' + InttoStr(Count * Page) + ', ' + InttoStr(Count) + ';';
//Execute Query
try
TableData := ScoreDB.GetTable(Query);
except
exit; // this has a try except, because ( on linux at least ) it seems that doing a GetTable, that returns nothing
// causes an exception. and in the case of a new Database file, with no scores stored yet... this seems to except here.
end;
//if Result empty -> Exit
if (TableData.RowCount < 1) then
exit;
//Copy Result to Stats Array
while not TableData.Eof do
begin
Stats[TableData.Row].Typ := Typ;
Case Typ of
0:begin
Stats[TableData.Row].Singer := TableData.Fields[0];
Stats[TableData.Row].Difficulty := StrtoIntDef(TableData.Fields[1], 0);
Stats[TableData.Row].Score := StrtoIntDef(TableData.Fields[2], 0){TableData.FieldAsInteger(2)};
Stats[TableData.Row].SongArtist := TableData.Fields[3];
Stats[TableData.Row].SongTitle := TableData.Fields[4];
end;
1:begin
Stats[TableData.Row].Player := TableData.Fields[0];
Stats[TableData.Row].AverageScore := StrtoIntDef(TableData.Fields[1], 0);
end;
2:begin
Stats[TableData.Row].Artist := TableData.Fields[0];
Stats[TableData.Row].Title := TableData.Fields[1];
Stats[TableData.Row].TimesSung := StrtoIntDef(TableData.Fields[2], 0);
end;
3:begin
Stats[TableData.Row].ArtistName := TableData.Fields[0];
Stats[TableData.Row].TimesSungtot := StrtoIntDef(TableData.Fields[1], 0);
end;
end;
TableData.Next;
end;
Result := True;
end;
//--------------------
//GetTotalEntrys - Get Total Num of entrys for a Stats Query
//--------------------
Function TDataBaseSystem.GetTotalEntrys(const Typ: Byte): Cardinal;
var Query: String;
begin
Result := 0;
if not assigned( ScoreDB ) then
exit;
try
//Create Query
Case Typ of
0: begin
Query := 'SELECT COUNT(`SongID`) FROM `'+cUS_Scores+'`;';
if not ScoreDB.TableExists( cUS_Scores ) then
exit;
end;
1: begin
Query := 'SELECT COUNT(DISTINCT `Player`) FROM `'+cUS_Scores+'`;';
if not ScoreDB.TableExists( cUS_Scores ) then
exit;
end;
2: begin
Query := 'SELECT COUNT(`ID`) FROM `'+cUS_Songs+'`;';
if not ScoreDB.TableExists( cUS_Songs ) then
exit;
end;
3: begin
Query := 'SELECT COUNT(DISTINCT `Artist`) FROM `'+cUS_Songs+'`;';
if not ScoreDB.TableExists( cUS_Songs ) then
exit;
end;
end;
Result := ScoreDB.GetTableValue(Query);
except
// TODO : JB_Linux - Why do we get these exceptions on linux !! -> should be solved!
on E:ESQLiteException DO
begin
exit;
end;
end;
end;
//--------------------
//GetStatReset - Get reset date of statistic data
//--------------------
Function TDataBaseSystem.GetStatReset: TDateTime;
var
Query: String;
TableData: TSqliteTable;
begin
if not assigned( ScoreDB ) then
exit;
Query := 'SELECT `ResetTime` FROM `'+cUS_Statistics_Info+'`;';
TableData := ScoreDB.GetTable(Query);
result:=StrToDateTime(TableData.Fields[0]);
end;
end.
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1185 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@972 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@970 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
See: http://trac2.assembla.com/usdx/ticket/35
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@966 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@870 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
- cleanup and adaption of SingDrawOscilloscope
Portaudio/SDL audio output:
- stuttering in portaudio output has been fixed (SDL_MixBuffers cannot be used
without initializing the SDL audio stuff first, so it is not usable with portaudio.
Now SDL is used for audio-output instead of portaudio (although the file-name is
UAudioPlayback_Portaudio.pas at the moment).
- cleaner file closing
- volume adjustment
UMusic:
- cleanup of the audio-interfaces
- introduced TNoteType = (ntFreestyle, ntNormal, ntGolden)
- some bug-fixes
- introduced TSoundLibrary. This is library for all in-game sounds used
by USDX. Instead of calling AudioPlayer.PlaySwoosh you should call
AudioPlayer.PlaySound(SoundLib.Swoosh) now. You might call SoundLib.Swoosh.Play
too, but this is not recommended at the moment because SoundLib.Swoosh could be nil
if the file was not found.
The SoundLibrary approach is much cleaner than the previous one. The AudioPlayer does
not have to specify a Play... and Stop... method for every available sound anymore.
In addition it is not an AudioPlayers responsibility to init the in-game sounds.
URecord:
- polish to english translation of some variables
- CaptureSoundLeft/Right is CaptureChannel[0/1] now
- TSoundCardInput -> TAudioInputDeviceSource
- TGenericSoundCard.Input -> TGenericSoundCard.Source
- autocorrelation algorithm more readable now
- Clean-up of the audio-input interface
- moved cloned code of the input-classes to one base class (TAudioInputBase)
- Cleaner finalization
- Start-/StopCapture will not crash anymore in the recording-options menu
- Fixed several bugs in the autocorrelation stuff (e.g. wrong usage of $10000)
- SzczytJest (now ToneValid) was not used correctly. ToneValid is set to true if
a valid tone was found (= the sound was louder than the threshold -> no background noise).
If i remember correctly the sound was accepted although the tone was invalid. So the old
data was used although noone was singing. This resulted in some sort of ghost-singer effect.
UIni:
- moved TIni.Card to TScreenOptionsRecord.Card because it is not stored in the ini-file and will not be in the future.
- TIni.CardList ist now TIni.InputDeviceConfig. The name cardlist was misleading because it just specifies input- but no output-devices. In addition a soundcard can have multiple input-devices (at least in linux).
- bugfix on InputDeviceConfig (formerly CardList) usage. USDX expected that the indices of the corresponding elements in TIni.InputDeviceConfig[] and TAudioInputProcessor.Device[] were the same. This is wrong. If device 2 was defined at first place in the ini and device 1 at the second, the indices of the two arrays didn't match (they were swapped) erroneously. To fix this and to support the item listed below the index to TIni.InputDeviceConfig[] is now stored in TAudioInputDevice.CfgIndex.
NOTE: InputDeviceConfig[] contains configurations of non-available (unplugged) devices. Iterate over TAudioInputProcessor.Device[] for available devices.
- configurations of external devices that are not plugged in will not be deleted anymore.
- multiple definitions of one device in the ini-file will not crash USDX anymore
- CardList[I].ChannelL/R now are InputDeviceConfig[I].ChannelToPlayerMap[0/1]. I think the new name is more intuitive because it maps a channel to a player number. Now the both vars are joint to one array. Now it is possible to use loops to process them and we might support more than two input channels on one device in the future (if such devices exist)
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@827 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
some changes to song loading...
Record global changed to singleton object
started implementing mic volume display in Settings-Record
hope this dosnt break anything.. :P
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@789 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
build:USDX-LAZLIN-75
build:USDX-LAZLIN-76
for some reason we can not use {$MODE Delphi} in an
included file.
( Probably because of the way the compier scopes this switch to each pas file )
ive had to revert this part of eddies changes.
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@548 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
windows/linux.
Added switches.inc to all files.
Changed many IFDEFs.
For Windows-only code please use MSWINDOWS instead of WIN32 now. WIN32 is also used by the Mac port.
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@546 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
Created "AudioPlayback" Global Singleton, which removed the need
for the "Music" Global variable. This was done because global singleton objects are
a recognized better "design pattern" achieving the same thing as global variables, but
in a nicer way.
I will be working to
a) separate IAudioPlayback in to separate "Playback" and "Input" Interfaces
b) build a FFMpeg class that implements IAudioPlayback
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@504 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
(using lazarus)
Bass etc is commented out..
but it compiles, and im working through the runtime errors.
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@408 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
minor tidy ups and removal of big old comment blocks..
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@394 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
screens ( Except where text input is required, and backspace is used for text input )
This has been done, so that when used with Windows Media Center IR Remotes ( needs SDL 1.2 dll at runtime ) users can navigate throug the game without sitting in front of the PC keyboard.
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@370 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@283 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
-> Better usability of VideoIcon
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@214 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
Updated Language Fiels to Fit with new Buttons
Some CodeClean Up in Menu Class and in Screens
Some minor Bug fixes I forgot about
Added ability to group Buttons within a Screen
New Theme Object:
ButtonCollection:
Same Attributes as a Button Plus
FirstChild: Defining the First Button in the Group.
For Example: SingSolo is 1, SingMulti Button is 2, in ScreenMain
Added Attribute to Theme Button:
Parent: Number of the assigned Group, 0 for no Group
Used new Abilitys in MainScreen
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@149 b956fd51-792f-4845-bead-9b4dfca2ff2c
|
|
Moved some Translated Strings from UScreenPartyOptions to UTheme to use it with the Statistic Screens, too.
Fixed use of /n Tag istead of the correct \n
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@118 b956fd51-792f-4845-bead-9b4dfca2ff2c
|